本文转载自:Android动态加载之插件开发 | BruceFan's Blog
动态加载可以用来进行插件开发,这些插件大概都是为了在一个主程序中实现比较通用的功能,使主程序具有可扩展性。实现原理是实现一套插件接口,把插件实现编成apk或dex,在运行时用DexClassLoader动态加载进来。
预备知识:Android中的动态加载机制
插件演示
这里用了三个项目:
- PInterface:插件接口项目(只是接口的定义)
- PImplement:插件项目(实现插件接口,定义具体的功能)
- HostProject:宿主项目(需要引用插件接口项目,然后动态加载插件项目)
项目介绍
下面看一下项目源代码:
1.PInterface项目
-
IBean.java
1 2 3 4 5 6
package 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 10
package 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 51
package 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() { @Override 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 15
package 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 199
package 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; @SuppressLint("NewApi") @Override 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() {//普通调用 反射的方式 @Override 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() {//带参数调用 @Override 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() {//带回调函数的调用 @Override 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() {//带资源文件的调用 @Override 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() {//带资源文件的调用 @Override 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() {//带资源文件的调用 @Override 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()); } @Override public AssetManager getAssets() { return mAssetManager == null ? super.getAssets() : mAssetManager; } @Override public Resources getResources() { return mResources == null ? super.getResources() : mResources; } @Override 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.youkuaiyun.com/jiangwei0910410003/article/details/41384667