动态加载可以用来进行插件开发,这些插件大概都是为了在一个主程序中实现比较通用的功能,使主程序具有可扩展性。实现原理是实现一套插件接口,把插件实现编成apk或dex,在运行时用DexClassLoader动态加载进来。
预备知识:Android中的动态加载机制
插件演示
这里用了三个项目:
- PInterface:插件接口项目(只是接口的定义)
- PImplement:插件项目(实现插件接口,定义具体的功能)
- HostProject:宿主项目(需要引用插件接口项目,然后动态加载插件项目)
项目介绍
下面看一下项目源代码:
1.PInterface项目
IBean.java
1
2
3
4
5
6package com.pluginsdk.interfaces;
public abstract interface IBean{
public abstract String getName();
public abstract void setName(String paramString);
}IDynamic.java
1
2
3
4
5
6
7
8
9
10package com.pluginsdk.interfaces;
import android.content.Context;
public abstract interface IDynamic{
public abstract void methodWithCallBack(YKCallBack paramYKCallBack);
public abstract void showPluginWindow(Context paramContext);
public abstract void startPluginActivity(Context context,Class<?> cls);
public abstract String getStringForResId(Context context);
}没有太多可说的,后面的代码可以下载项目之后自己看。
2.PImplement项目
- Dynamic.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51package com.pluginsdk.pimplement;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import com.pluginsdk.R;
import com.pluginsdk.bean.Bean;
import com.pluginsdk.interfaces.IDynamic;
import com.pluginsdk.interfaces.YKCallBack;
public class Dynamic implements IDynamic{
public void methodWithCallBack(YKCallBack callback) {
Bean bean = new Bean();
bean.setName("PLUGIN_SDK_USER");
callback.callback(bean);
}
public void showPluginWindow(Context context) {
AlertDialog.Builder builder = new Builder(context);
builder.setMessage("对话框");
builder.setTitle(R.string.hello_world);
builder.setNegativeButton("取消", new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
Dialog dialog = builder.create();//.show();
dialog.show();
}
public void startPluginActivity(Context context, Class<?> cls){
/*
* 这里要注意几点:
* 1、如果单纯的写一个MainActivity的话,在主项目中也有一个MainActivity,开启的Activity还是主项目中的MainActivity
* 2、如果这里将MainActivity写成全名的话,还是有问题,会报找不到这个Activity的错误
*/
Intent intent = new Intent(context, cls);
context.startActivity(intent);
}
public String getStringForResId(Context context){
return context.getResources().getString(R.string.hello_world);
}
} - Bean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.pluginsdk.bean;
public class Bean implements com.pluginsdk.interfaces.IBean{
private String name = "这是来自于插件项目中设置的初始化的名字";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.宿主项目HostProject
- MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199package com.plugindemo;
import java.io.File;
import java.lang.reflect.Method;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import com.pluginsdk.interfaces.IBean;
import com.pluginsdk.interfaces.IDynamic;
import com.pluginsdk.interfaces.YKCallBack;
import dalvik.system.DexClassLoader;
public class MainActivity extends Activity {
private AssetManager mAssetManager;//资源管理器
private Resources mResources;//资源
private Theme mTheme;//主题
private String apkFileName = "Pimplement.apk";
private String dexpath = null;//apk文件地址
private File fileRelease = null; //释放目录
private DexClassLoader classLoader = null;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dexpath = getApplicationContext().getFilesDir().getAbsolutePath() + File.separator + apkFileName;
fileRelease = getDir("dex", 0);
/*初始化classloader
* dexpath dex文件地址
* fileRelease 文件释放地址
* 父classLoader
*/
classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(), null, getClassLoader());
Button btn_1 = (Button)findViewById(R.id.btn_1);
Button btn_2 = (Button)findViewById(R.id.btn_2);
Button btn_3 = (Button)findViewById(R.id.btn_3);
Button btn_4 = (Button)findViewById(R.id.btn_4);
Button btn_5 = (Button)findViewById(R.id.btn_5);
Button btn_6 = (Button)findViewById(R.id.btn_6);
btn_1.setOnClickListener(new View.OnClickListener() {//普通调用 反射的方式
public void onClick(View v) {
Class mLoadClassBean;
try {
mLoadClassBean = classLoader.loadClass("com.pluginsdk.bean.Bean");
Object beanObject = mLoadClassBean.newInstance();
Log.d("DEMO", "ClassLoader:" + mLoadClassBean.getClassLoader());
Log.d("DEMO", "ClassLoader:" + mLoadClassBean.getClassLoader().getParent());
Method getNameMethod = mLoadClassBean.getMethod("getName");
getNameMethod.setAccessible(true);
String name = (String) getNameMethod.invoke(beanObject);
Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_2.setOnClickListener(new View.OnClickListener() {//带参数调用
public void onClick(View arg0) {
Class mLoadClassBean;
try {
mLoadClassBean = classLoader.loadClass("com.pluginsdk.bean.Bean");
Object beanObject = mLoadClassBean.newInstance();
//接口形式调用
Log.d("DEMO", beanObject.getClass().getClassLoader()+"");
Log.d("DEMO",IBean.class.getClassLoader()+"");
Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");
IBean bean = (IBean)beanObject;
bean.setName("宿主程序设置的新名字");
Toast.makeText(MainActivity.this, bean.getName(), Toast.LENGTH_SHORT).show();
}catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_3.setOnClickListener(new View.OnClickListener() {//带回调函数的调用
public void onClick(View arg0) {
Class mLoadClassDynamic;
try {
mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.pimplement.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
//接口形式调用
IDynamic dynamic = (IDynamic)dynamicObject;
//回调函数调用
YKCallBack callback = new YKCallBack() {//回调接口的定义
public void callback(IBean arg0) {
Toast.makeText(MainActivity.this, arg0.getName(), Toast.LENGTH_SHORT).show();
};
};
dynamic.methodWithCallBack(callback);
} catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_4.setOnClickListener(new View.OnClickListener() {//带资源文件的调用
public void onClick(View arg0) {
loadResources();
Class mLoadClassDynamic;
try {
mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.pimplement.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
//接口形式调用
IDynamic dynamic = (IDynamic)dynamicObject;
dynamic.showPluginWindow(MainActivity.this);
} catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_5.setOnClickListener(new View.OnClickListener() {//带资源文件的调用
public void onClick(View arg0) {
loadResources();
Class mLoadClassDynamic;
try {
mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.pimplement.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
//接口形式调用
IDynamic dynamic = (IDynamic)dynamicObject;
dynamic.startPluginActivity(MainActivity.this,
classLoader.loadClass("com.plugindemo.MainActivity"));
} catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_6.setOnClickListener(new View.OnClickListener() {//带资源文件的调用
public void onClick(View arg0) {
loadResources();
Class mLoadClassDynamic;
try {
mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.pimplement.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
//接口形式调用
IDynamic dynamic = (IDynamic)dynamicObject;
String content = dynamic.getStringForResId(MainActivity.this);
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();
}
Resources superRes = super.getResources();
superRes.getDisplayMetrics();
superRes.getConfiguration();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
public Resources getResources() {
return mResources == null ? super.getResources() : mResources;
}
public Theme getTheme() {
return mTheme == null ? super.getTheme() : mTheme;
}
}
项目引用关系
1.将接口项目PInterface设置成一个Library,
并导出项目为一个jar。
2.插件项目PImplement引用接口项目的jar
注意是lib文件夹,而不是libs,在Android中的动态加载机制
中说过这样做的原因:插件项目打包不能集成接口jar,宿主项目打包一定要集成接口jar。
3.HostProject项目引用PInterface这个Library
项目引用完成后,我们编译PImplement项目,生成PImplement.apk放到/data/data/com.plugindemo/files,因为代码中是从这个目录进行加载的,这个目录是可以修改的。运行HostProject
运行成功,这个对话框其实是在插件中定义的。
项目下载
reference
http://blog.csdn.net/jiangwei0910410003/article/details/41384667