Activity and resource are like twin brothers. And so if the activity need to be solve in Plug-In completely, you will have to face how to use the resource in it.

In this chapter, we start with loading mechanism of the resource, and further discusses the Plug-In of resources through the addAssetPath method of AssetManager. Finally, we implement a skinning technique based on this solution.

7.1 How to load resources in Android

7.1.1 Kinds of resources

The resource files in Android fall into two categories.

One is under ‘res’ folder which can be compiled. During the compile process, ‘R.java’ file will be created, it contains the hex value of each resource file, here is an example below:

It is easy to access these resources. You will get an instance of ‘Resources’ by the method of ‘getResources()’ in ‘Context’ class, then fetch any resources through ‘getXXX()’ method of this instance, just like below:

Resources resources = getResources();

String appName = resources.getString(R.string.app_name);

The other is stored under assets folder, since the file will not be compile here during the compile process, so we cannot access them through ‘R.java’. Then can we access them from absolute path? The answer is no, because the ‘apk’ file won’t be uncompress in local after downloading. That’s why we cannot fetch the absolute path of ‘assets’ folder.

Now it is the only way to use ‘open()’ method of ‘AssetManager’ class to fetch them. And you can get ‘AssetManager’ instance from ‘getAssets()’ method of ‘Resources’ class. So, the code will be like below:

Resources resources = getResources();

AssetManager am = getResources().getAssets();

InputStream is = getResources().getAssets().open("filename");

This shows that ‘Resources’ class can do the everything.

7.1.2 Resources and AssetManager

‘Resources’ class is like sales, but ‘AssetManager’ class is like developer. sales are external and developer is not.

So we can find that ‘Resource’ class provide many methods like ‘getString()’, ’getText()’, ’getDrawable()’ and so on. In fact, all of these methods call the private method of ‘AssetManager’ class indirectly, and ‘AssetManager’ do the job of query the resources.

It is injustice for ‘AssetManager’, as it does lots of work but few people know, it only has two public methods. For example, ‘open()’ is used for access the resources in ‘assets’ folder.

There has a ‘addAssetPath(String path)’ method in ‘AssetManager’ class, it will set the current APK path as variable when an APP launched, then ‘AssetManager’ and ‘Resources’ can access all the resources of this APK.

‘addAssetPath()’ is not a public method, we can use reflection, put the path of plugin APK into this method, then the resources of plugin APK were added into a resource pool. The current APP resource is already in here too.

How many Plug-In Apps are there, how many times the ‘addAssetPath()’ method need to be execute. All the Plug-In resource need to put in the resource pool.

7-1 Resource and AssetManger

There is a NDK method in AssetManager for accessing resource files. During packaging APK file, it will generate a HEX value for each resources in ‘R.java’ class. But when app is running, how do we know which file or which resource is corresponding to HEX value?

There is a file named ‘resources.arsc’ will be generate during package time, it contains a Hash table, which has the corresponding relationship of resources and HEX value.

7.2 Plug-In Solution of Resources

Let’s try to read a String resource from Plug-In App in Host App.

1) First, see the code from Plug-In App called ‘Plugin1’:

public class Dynamic implements IDynamic {

@Override

public String getStringForResId(Context context) {

return context.getResources().getString(R.string.myplugin1_hello_world);

}

}

In Plugin1, there is a ‘strings.xml’ exists under ‘res/values’ folder and a string resource was defined here:

<resources>

<string name=" myplugin1_hello_world">Hello World</string>

</resources>

2) Second, let’s see the code of ‘MainActivity’ in Host App

public class MainActivity extends AppCompatActivity {

private AssetManager mAssetManager;

private Resources mResources;

private Resources.Theme mTheme;

private String dexpath = null; //apk file path

private File fileRelease = null; //decompress folder

private DexClassLoader classLoader = null;

private String apkName = "plugin1.apk"; //apk file name

TextView tv;

@Override

protected void attachBaseContext(Context newBase) {

super.attachBaseContext(newBase);

try {

Utils.extractAssets(newBase, apkName);

} catch (Throwable e) {

e.printStackTrace();

}

}

@SuppressLint("NewApi")

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

File extractFile = this.getFileStreamPath(apkName);

dexpath = extractFile.getPath();

fileRelease = getDir("dex", 0); //0 means ‘Context.MODE_PRIVATE’

classLoader = new DexClassLoader(dexpath,

fileRelease.getAbsolutePath(), null, getClassLoader());

Button btn_6 = (Button) findViewById(R.id.btn_6);

tv = (TextView)findViewById(R.id.tv);

// The calling of resource files

btn_6.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View arg0) {

loadResources();

Class mLoadClassDynamic = null;

try {

mLoadClassDynamic = classLoader.loadClass("jianqiang.com.plugin1.Dynamic");

Object dynamicObject = mLoadClassDynamic.newInstance();

IDynamic dynamic = (IDynamic) dynamicObject;

String content = dynamic.getStringForResId(MainActivity.this);

tv.setText(content);

Toast.makeText(getApplicationContext(), content + "", Toast.LENGTH_LONG).show();

} catch (Exception e) {

Log.e("DEMO", "msg:" + e.getMessage());

}

}

});

}

protected void loadResources() {

try {

AssetManager assetManager = AssetManager.class.newInstance();

Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);

addAssetPath.invoke(assetManager, dexpath);

mAssetManager = assetManager;

} catch (Exception e) {

e.printStackTrace();

}

mResources = new Resources(mAssetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());

mTheme = mResources.newTheme();

mTheme.setTo(super.getTheme());

}

@Override

public AssetManager getAssets() {

if(mAssetManager == null) {

return super.getAssets();

}

return mAssetManager;

}

@Override

public Resources getResources() {

if(mResources == null) {

return super.getResources();

}

return mResources;

}

@Override

public Resources.Theme getTheme() {

if(mTheme == null) {

return super.getTheme();

}

return mTheme;

}

}

The logic of code above are divided into 4 parts:

1) ‘LoadResources’ method

Create a ‘AssetManager’ instance by reflection, call ‘addAssetPath’ method and add the path of Plug-In App into the ‘AssetManager’.

That’s it! From now on, this ‘AsssetManager’ instance has only served ‘Plugin1’.

Then base on this ‘AssetManager’, we can create corresponding ‘Resources’ and ‘Theme’.

2) Override ‘getAsset’, ‘getResources’ and ‘getTheme’ methods. The logic is almost same, take getAsset as an example in below:

@Override

public AssetManager getAssets() {

if(mAssetManager == null) {

return super.getAssets();

}

return mAssetManager;

}

‘mAssetManager’ is point to Plug-In App, if this object is null, we can call the ‘getAsset’ method in its parent class – ‘ContextImpl’, and this time, the ‘AssetManager’ object, is point to Host App and the resources we fetch is also belongs to Host App.

3) Load the external Plug-In App, generate the corresponding ‘Classloader’:

File extractFile = this.getFileStreamPath(apkName);

dexpath = extractFile.getPath();

fileRelease = getDir("dex", 0); //0 means Context.MODE_PRIVATE

classLoader = new DexClassLoader(dexpath,

fileRelease.getAbsolutePath(), null, getClassLoader());

4) Thanks to the reflection, we can use the classes in Plug-In App, generate ‘dynamicObject’ object from it, then we can fetch resources in Plug-In App.

loadResource();

Class mLoadClassDynamic = classLoader.loadClass("jianqiang.com.plugin1.Dynamic");

Object dynamicObject = mLoadClassDynamic.newInstance();

IDynamic dynamic = (IDynamic) dynamicObject;

String content = dynamic.getStringForResId(MainActivity.this);

tv.setText(content);

Now, we find a perfect solution to load resources in Plug-In App.

We have notice that there is a little bit confusing in source code of Dynamic1.3, all the logic is in the ‘MainActivity’ of Host App. So, it is need to be refactored. Please refer to the project called ’Dynamic2’, the common code was in ‘BaseActivity’. For example we refactored ‘getAsset’, ‘getResource’ and ‘getTheme’ methods, and load the 3rd-party apk into resource pool.

7.3 Skin changing by Plug-in

If you’ve ever used Mobile QQ, you will find there is a feature to change skin.

If you’ve ever played Glory of the king, you will notice that you can see new heroes without updating APP.

The emoji in WeChat can be downloaded and used immediately.

In fact, all the features above is to replace the image, with the new resource files from the package just downloaded.

A simple but rough solution is to compress these images into a ZIP, and uncompressed to a folder after downloading, then you can use it.

Since we have the Plug-In technology, we find that we can put all the images into Plug-In App and read each of them by ‘R.java’.

Let’s continue to complete this function base on ‘Dynamic1.2’ .

1) Do some interest working in ‘plugin1’

We write a util class called ‘UIUtil’ and it provides ‘getText’, ‘getImage’ and ‘getLayout’ methods, which can fetch string, image and layout from ‘R.java’.

public class UIUtil {

public static String getTextString(Context ctx){

return ctx.getResources().getString(R.string.hello_message);

}

public static Drawable getImageDrawable(Context ctx){

return ctx.getResources().getDrawable(R.drawable.robert);

}

public static View getLayout(Context ctx){

return LayoutInflater.from(ctx).inflate(R.layout.main_activity, null);

}

}

Then put some resources into it:

· Put an image into ‘res/drawable’, the file name is ‘robert.png’, and the image content just like below:

· Add string value into ‘strings.xml’ which belongs to ‘res/values’, named ‘hello_message’.

<string name="hello_message">Hello</string>

· Modify the file ‘main_activity.xml’ in ‘res/layout’ and make three buttons placed horizontally.

After compile, rename the apk file to ‘plugin1.apk’ and put it into ‘assets’ folder in the Host App.

2) Make ‘Plungin2’ rapidly

Copy the entire project code of ‘Plugin1’ and rename to ‘Plugin2’. No need to modify anything.

Modify the resources of ‘Plugin2’

· Rotate the robert.png image 180 degrees.

· Change ‘hello_message’ string value to ‘你好’

· Change layout xml code, make three buttons placed vertically.

· After compile, rename the apk file to ‘plugin2.apk’ and put it into ‘assets’ folder in the Host App.

3) Work with Host App

Based on ‘Dynamic1.2’, let’s continue.

Firstly, move the common methods to ‘BaseActivity’, these includes

· Load 2 plugins

· Generate 2 ‘ClassLoader’

· Override ‘getAssets’, ’getResources’ and ‘getTheme’.

· ‘loadResources’ was increased to 2 methods because there are 2 Plug-In Apps now.

The code will be like this:

public class BaseActivity extends Activity {

private AssetManager mAssetManager;

private Resources mResources;

private Resources.Theme mTheme;

private String dexpath1 = null; //apk file path

private String dexpath2 = null; //apk file path

private File fileRelease = null; //decompression path

protected DexClassLoader classLoader1 = null;

protected DexClassLoader classLoader2 = null;

TextView tv;

@Override

protected void attachBaseContext(Context newBase) {

super.attachBaseContext(newBase);

Utils.extractAssets(newBase, "plugin1.apk");

Utils.extractAssets(newBase, "plugin2.apk");

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

fileRelease = getDir("dex", 0);

File extractFile1 = this.getFileStreamPath("plugin1.apk");

dexpath1 = extractFile1.getPath();

classLoader1 = new DexClassLoader(dexpath1, fileRelease.getAbsolutePath(), null, getClassLoader());

File extractFile2 = this.getFileStreamPath("plugin2.apk");

dexpath2 = extractFile2.getPath();

classLoader2 = new DexClassLoader(dexpath2, fileRelease.getAbsolutePath(), null, getClassLoader());

}

protected void loadResources1() {

try {

AssetManager assetManager = AssetManager.class.newInstance();

Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);

addAssetPath.invoke(assetManager, dexpath1);

mAssetManager = assetManager;

} catch (Exception e) {

e.printStackTrace();

}

Resources superRes = super.getResources();

mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());

mTheme = mResources.newTheme();

mTheme.setTo(super.getTheme());

}

protected void loadResources2() {

try {

AssetManager assetManager = AssetManager.class.newInstance();

Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);

addAssetPath.invoke(assetManager, dexpath2);

mAssetManager = assetManager;

} catch (Exception e) {

e.printStackTrace();

}

Resources superRes = super.getResources();

mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());

mTheme = mResources.newTheme();

mTheme.setTo(super.getTheme());

}

@Override

public AssetManager getAssets() {

return mAssetManager == null ? super.getAssets() : mAssetManager;

}

@Override

public Resources getResources() {

return mResources == null ? super.getResources() : mResources;

}

@Override

public Resources.Theme getTheme() {

return mTheme == null ? super.getTheme() : mTheme;

}

}

Secondly, let ResourceActivity inherit from BaseActivity, click Button1 to load the skin of plugin1 and click Button2 to load the skin of plugin2:

public class ResourceActivity extends BaseActivity {

/**

* The user control need to be change skin

* List 3 examples : TextView,ImageView,LinearLayout

*/

private TextView textV;

private ImageView imgV;

private LinearLayout layout;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_resource);

textV = (TextView) findViewById(R.id.text);

imgV = (ImageView) findViewById(R.id.imageview);

layout = (LinearLayout) findViewById(R.id.layout);

findViewById(R.id.btn1).setOnClickListener(new OnClickListener() {

@Override

public void onClick(View arg0) {

loadResources1();

doSomething1();

}

});

findViewById(R.id.btn2).setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

loadResources2();

doSomething2();

}

});

}

private void doSomething1() {

try {

Class clazz = classLoader1.loadClass("jianqiang.com.plugin1.UIUtil");

String str = (String) RefInvoke.invokeStaticMethod(clazz, "getTextString", Context.class, this);

textV.setText(str);

Drawable drawable = (Drawable) RefInvoke.invokeStaticMethod(clazz, "getImageDrawable", Context.class, this);

imgV.setBackground(drawable);

layout.removeAllViews();

View view = (View) RefInvoke.invokeStaticMethod(clazz, "getLayout", Context.class, this);

layout.addView(view);

} catch (Exception e) {

Log.e("DEMO", "msg:" + e.getMessage());

}

}

private void doSomething2() {

try {

Class clazz = classLoader2.loadClass("jianqiang.com.plugin1.UIUtil");

String str = (String) RefInvoke.invokeStaticMethod(clazz, "getTextString", Context.class, this);

textV.setText(str);

Drawable drawable = (Drawable) RefInvoke.invokeStaticMethod(clazz, "getImageDrawable", Context.class, this);

imgV.setBackground(drawable);

layout.removeAllViews();

View view = (View) RefInvoke.invokeStaticMethod(clazz, "getLayout", Context.class, this);

layout.addView(view);

} catch (Exception e) {

Log.e("DEMO", "msg:" + e.getMessage());

}

}

}

Run Host App, click ‘Button1’ and the theme in ‘Plugin1’ will be show:

7-2 The effect of click ‘Button1’

Click ‘Button2’ and the theme in ‘Plugin2’ will be show:

7-3 The effect of click ‘Button2’

But there is too much duplicate code in HostApp, it just for your easy understand, not elegant. In addition, as the number of plugin skins increases, the redundant code will be more and more. So it is necessary to throw them into a ‘HashMap’ for maintenance.

Here is the code of ‘BaseActivity’:

public class BaseActivity extends Activity {

private AssetManager mAssetManager;

private Resources mResources;

private Resources.Theme mTheme;

protected HashMap<String, PluginInfo> plugins = new HashMap<String, PluginInfo>();

@Override

protected void attachBaseContext(Context newBase) {

super.attachBaseContext(newBase);

Utils.extractAssets(newBase, "plugin1.apk");

Utils.extractAssets(newBase, "plugin2.apk");

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

genegatePluginInfo("plugin1.apk");

genegatePluginInfo("plugin2.apk");

}

protected void genegatePluginInfo(String pluginName) {

File extractFile = this.getFileStreamPath(pluginName);

File fileRelease = getDir("dex", 0);

String dexpath = extractFile.getPath();

DexClassLoader classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(), null, getClassLoader());

plugins.put(pluginName, new PluginInfo(dexpath, classLoader));

}

protected void loadResources(String dexPath) {

try {

AssetManager assetManager = AssetManager.class.newInstance();

Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);

addAssetPath.invoke(assetManager, dexPath);

mAssetManager = assetManager;

} catch (Exception e) {

e.printStackTrace();

}

Resources superRes = super.getResources();

mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());

mTheme = mResources.newTheme();

mTheme.setTo(super.getTheme());

}

@Override

public AssetManager getAssets() {

return mAssetManager == null ? super.getAssets() : mAssetManager;

}

@Override

public Resources getResources() {

return mResources == null ? super.getResources() : mResources;

}

@Override

public Resources.Theme getTheme() {

return mTheme == null ? super.getTheme() : mTheme;

}

}

And here is the code of ‘ResourceActivity’:

public class ResourceActivity extends BaseActivity {

/**

* The widgets that need to replace the theme

* The example of them: TextView,ImageView,LinearLayout

*/

private TextView textV;

private ImageView imgV;

private LinearLayout layout;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_resource);

textV = (TextView) findViewById(R.id.text);

imgV = (ImageView) findViewById(R.id.imageview);

layout = (LinearLayout) findViewById(R.id.layout);

findViewById(R.id.btn1).setOnClickListener(new OnClickListener() {

@Override

public void onClick(View arg0) {

PluginInfo pluginInfo = plugins.get("plugin1.apk");

loadResources(pluginInfo.getDexPath());

doSomething(pluginInfo.getClassLoader());

}

});

findViewById(R.id.btn2).setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

PluginInfo pluginInfo = plugins.get("plugin2.apk");

loadResources(pluginInfo.getDexPath());

doSomething(pluginInfo.getClassLoader());

}

});

}

private void doSomething(ClassLoader cl) {

try {

Class clazz = cl.loadClass("jianqiang.com.plugin1.UIUtil");

String str = (String) RefInvoke.invokeStaticMethod(clazz, "getTextString", Context.class, this);

textV.setText(str);

Drawable drawable = (Drawable) RefInvoke.invokeStaticMethod(clazz, "getImageDrawable", Context.class, this);

imgV.setBackground(drawable);

layout.removeAllViews();

View view = (View) RefInvoke.invokeStaticMethod(clazz, "getLayout", Context.class, this);

layout.addView(view);

} catch (Exception e) {

Log.e("DEMO", "msg:" + e.getMessage());

}

}

}

To change skin by using Plug-In way is very easy. ‘Plugin1’ is a template, there is no need to change any code when we create a new skin, just replace the resources under ‘res’ folder, and do not need to modify the resources files name.

That’s it! Have you learned?

7.4 Another solution of Skin changing

In this section, we talk about ‘Dynamic3.2’, it based on ‘Dynamic 3.1’, and modify ‘doSomething’ method of ‘ResourceActivity’.

The example in last section, we use ‘R.drawable.robert’ to access resource in Plug-In.

In fact, we can access the inner class - ‘R.java’ in Plug-In in the HostApp directly. With the Hex value, the image can be fetch by the use of ‘getDrawable(resId)’. In this way, ‘getResources’ method will provide the resources in Plug-In App.

Class stringClass = cl.loadClass("jianqiang.com.plugin1.R$string");

int resId1 = (int) RefInvoke.getStaticFieldObject(stringClass, "hello_message");

textV.setText(getResources().getString(resId1));

Class drawableClass = cl.loadClass("jianqiang.com.plugin1.R$drawable");

int resId2 = (int) RefInvoke.getStaticFieldObject(drawableClass, "robert");

imgV.setBackground(getResources().getDrawable(resId2));

Class layoutClazz = cl.loadClass("jianqiang.com.plugin1.R$layout");

int resId3 = (int) RefInvoke.getStaticFieldObject(layoutClazz, "main_activity");

View view = (View) LayoutInflater.from(this).inflate(resId3, null);

layout.removeAllViews();

layout.addView(view);

From now on, there is no need for ‘UIUtil’ class in Plug-In App, it is only a APK contains resource files and ‘R’ file.

7.5 Summary

This chapter gives a detailed description of the principle of resources. Based on this, we use reflection, use ‘addAssetPath’ of ‘AssetManager’ to load resources in Plug-In App.

And skin changing is the specific implementation of resource Plug-In.

Chapter 7 Resources in Plug-In(1)的更多相关文章

  1. 译:Spring框架参考文档之IoC容器(未完成)

    6. IoC容器 6.1 Spring IoC容器和bean介绍 这一章节介绍了Spring框架的控制反转(IoC)实现的原理.IoC也被称作依赖注入(DI).It is a process wher ...

  2. Android二维码开源项目zxing编译

    ZXing是一个开放源代码的,用Java实现的多种格式的1D/2D条码图像处理库,它包括了联系到其它语言的port.Zxing能够实现使用手机的内置的摄像头完毕条形码的扫描及解码.该项目可实现的条形码 ...

  3. Spring4参考手册中文版

    Spring4参考手册中文版 前言 https://github.com/b2gats/stone-docs/blob/master/spring-4-beans.md Part III. 核心技术 ...

  4. Android Programming: Pushing the Limits -- Chapter 3: Components, Manifests, and Resources

    Android Components Manifest文件 Resource and Assets v\:* {behavior:url(#default#VML);} o\:* {behavior: ...

  5. TIJ——Chapter One:Introduction to Objects

    ///:~容我对这个系列美其名曰"读书笔记",其实shi在练习英文哈:-) Introduction to Objects Object-oriented programming( ...

  6. Chapter 5: Container

    Chapter 5: Container A container is a module that processes the requests for a servlet and populates ...

  7. 如何写出优秀的研究论文 Chapter 1. How to Write an A+ Research Paper

    This Chapter outlines the logical steps to writing a good research paper. To achieve supreme excelle ...

  8. Chapter 2 - How to Add a sprite

    Chapter 2 - How to Add a sprite 1. Add image resources 1.1add resources on win32 2. Add a sprite TIP ...

  9. Chapter 6 — Improving ASP.NET Performance

    https://msdn.microsoft.com/en-us/library/ff647787.aspx Retired Content This content is outdated and ...

随机推荐

  1. [程序员代码面试指南]数组和矩阵问题-找到无序数组中最小的k个数(堆排序)

    题目链接 https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&t ...

  2. [剑指Offer]10-斐波那契数列(循环)-Java

    题解 使用循环,时间复杂度O(n). 相关 跳台阶:f(n)=f(n-1)+f(n-2) 变态跳台阶:f(n)=2*f(n-1) 矩形覆盖:f(n)=f(n-1)+f(n-2) 全部用循环代替递归,使 ...

  3. pandas的一些

    在具体谈及骚操作之前先捋一遍基本的统计特征函数 方法名 函数功能 所属库 sum() 计算数据样本的综合(按照列计算) pandas mean() 计算数据样本的算术平均数 pandas var() ...

  4. Vue源码学习(二)$mount() 后的做的事(1)

    Vue实例初始化完成后,启动加载($mount)模块数据. (一)Vue$3.protype.$mount             标红的函数 compileToFunctions 过于复杂,主要是生 ...

  5. [转] spring framework体系结构及内部各模块jar之间的maven依赖关系

    很多人都在用spring开发java项目,但是配置maven依赖的时候并不能明确要配置哪些spring的jar,经常是胡乱添加一堆,编译或运行报错就继续配置jar依赖,导致spring依赖混乱,甚至下 ...

  6. springmvc webservlet 加redis 订阅消息

    由于项目内请求的数据需要 等待设备处理完毕后返回才能得到.请求命令返回的是发送成功的包,而不是设备处理的包,所以需要请求等待.. 方式一:项目中 添加了redis作为一个缓存, webservlet ...

  7. 让终端走socks5代理

    (2017.9.17更新) 方法1: 在终端中直接运行命令 1 export http_proxy=http://proxyAddress:port 这个办法的好处是简单直接,并且影响面很小(只对当前 ...

  8. VB 读取列表文件名

    Private Sub Command1_Click()Dim d As String, s As String If Dir(App.Path & "\down", vb ...

  9. django model设计与实际数据库表的对比

    # 文章class Article(models.Model): title = models.CharField('标题', max_length=70) excerpt = models.Text ...

  10. ES6 学习之旅

    最常用的ES6特性 1.let, const 2.class, extends, super 3.arrow function (箭头函数) 4.template string (用反引号(`)来标识 ...