Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法

最近完全投入Android开发一年左右了,中间也是一直补知识。到现在,还是补了蛮多的。 布局上用约束布局很爽,应该没啥大问题。 负责的布局,rv多type用的多,另外阿里的Vlayout也有尝试,还有一些其他框架,有看过一些三方框架源码,貌似也是多布局的封装,还蛮骚的样子。自定义View之前搞过,流程基本ok,问题不会太大。然后到了后面自己封装了弹窗库,新项目也用到了(近期弹窗计划正在针对地区选择进行封装,封装后正好下一个版本迭代用上),另外Android公共组件库正在考虑中,因为做了几个项目,基本很多控件都是类似的配置,而且有些还是很重复的操作,所以打算再搞一个公共组件库(当然其中包括涉及到自定义View、方便用户配置)。简单回味下....
然后一方面小萌新再看一些源码,一方面打算抽点时间再深入下其他方面,比如插件化、热修复等,想想还是蛮重要的勒!
插件化的原理相关介绍:
1. 通过DexClassLoader加载。
2. 代理模式添加生命周期。
3. Hook思想跳过清单验证。
好吧,先尝试实践下DexClassLoader加载吧,参考网友的操作我们来过一下流程! 后面就开始着手做一些较深入的分析,顺便结合相关官方资料来加深印象!
Tips: DexClassLoader.loadClass()加载后可以如下方式调用插件的方法
//通过反射调用插件的代码
//通过接口调用插件的代码(其中包括较为完善的面向切面编程调用插件的方法)
**A. **试试反射的方式:
1. 创建工程
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 2. 新建一个Module- plugin
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 3. 然后plugin模块下新建一个被调用的方法,比如 PluginTest.java, 并提供如下操作

public class PluginTest { private String feature = "不帅"; public String getFeature() { return feature; }public void setFeature(String feature) { this.feature = feature; } }

4. 然后打包这个模块为apk
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 5. 将plugin下的apk拷贝到app模块下的assets目录下
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 6. 搞工具类将assets目录下的plugin-debug.apk拷贝到应用目录下,比如/data/user/0/popeeee.hl.com.plugin/files/Download/下,这样可以避免还需要动态申请存储权限的问题
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 7. 然后就可以进行拷贝操作了哟,成功后进行apk的装载
import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import popeeee.hl.com.plugin.utils.FileUtil; import popeeee.hl.com.plugin.utils.SystemUtils; public class MainActivity extends AppCompatActivity { private String pluginApkName = "plugin-debug.apk"; ///< 插件apk名称 private String apkPath; ///< apk存储路径 private String apkDexPath; ///< apk解压dex的目录、和apk存放路径为一个路径 private DexClassLoader dexClassLoader; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ///< 获取apk准备存储的应用本地缓存路径 this.apkDexPath = SystemUtils.getCacheDirectory(this, Environment.DIRECTORY_DOWNLOADS).getPath(); ///< 拷贝assets下的plugin-debug.apk到apkPath目录并获取实际路径 this.apkPath = FileUtil.copyFilesFromAssets(this, pluginApkName, apkDexPath); ///< 加载apk并获取DexClassLoader对象 this.dexClassLoader = new DexClassLoader(apkPath, apkDexPath, null, this.getClassLoader()); } }

8. 给当前控件添加一个点击事件,然后点击通过DexClassLoader.loadClass()加载插件对应的类,然后通过反射获取对应的方法进行调用, 之前关于反射的学习MonkeyLei:Android-自定义注解-控件注解等
/** * 默认hello world文本框添加点击事件 android:onClick="CallPlugin" * @param view */ public void CallPlugin(View view) { try { ///< 加载插件的类(插件的包名.类名) Class mClass = dexClassLoader.loadClass("popeeee.hl.com.plugin.PluginTest"); ///< 获取类的实例 Object beanObject = mClass.newInstance(); ///< 然后通过反射获取对应的方法 Method setFeatureMethod = mClass.getMethod("setFeature", String.class); setFeatureMethod.setAccessible(true); Method getFeatureMethod = mClass.getMethod("getFeature"); getFeatureMethod.setAccessible(true); ///< 然后执行对应方法进行相关设置和获取 setFeatureMethod.invoke(beanObject, "丑的不行呀!"); String feature = (String) getFeatureMethod.invoke(beanObject); ///< 然后本地进行一些提示等操作 Toast.makeText(this, feature, Toast.LENGTH_SHORT).show(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }

9. 当点击hello world后就可以看见回调信息了呀。。。
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 以上方式加载过程都ok。 不过很多人都是把拷贝apk放到如下地方进行调用(其实拷贝很快的,不一定要放到这里?ContextWrapper类的源码,ContextWrapper中有一个attachBaseContext()方法,这个方法会将传入的一个Context参数赋值给mBase对象,之后mBase对象就有值了。
Application中在onCreate()方法里去初始化各种全局的变量数据是一种比较推荐的做法,但是如果你想把初始化的时间点提前到极致,也可以去重写attachBaseContext()方法),同时加载apk时进行一个简单判断:
@Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); ///< 获取apk准备存储的应用本地缓存路径 this.apkDexPath = SystemUtils.getCacheDirectory(this, Environment.DIRECTORY_DOWNLOADS).getPath(); ///< 拷贝assets下的plugin-debug.apk到apkPath目录并获取实际路径 this.apkPath = FileUtil.copyFilesFromAssets(this, pluginApkName, apkDexPath); }@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ///< 判断apk是否存在 File file = new File(apkPath); if (!file.exists()){ Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); return; } ///< 加载apk并获取DexClassLoader对象,如果有.so需要考虑第三个参数 this.dexClassLoader = new DexClassLoader(apkPath, apkDexPath, null, this.getClassLoader()); }

B. 上面的加载方法还是略显复杂,有点麻烦了,如果加载的对象可以直接转换为PluginTest对象岂不是妙哉!
由于app模块并没有这个PluginTeset类,所以没法这样操作,有个做法是,把插件的类复制一份到app模块,然后直接强制转换即可!试试是可以滴了....
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 这样也是没问题的。但是这样很麻烦呀,你想想,一旦插件要加个什么东西都需要拷贝一份,太烦了。 所以我们需要一个公共库,宿主和插件都依赖它,然后由它提供相关的实体类接口,这样只要都继承对应接口即可,维护起来也方便很多呢!
1. 新建一个插件库(主要是与插件对应)
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 【Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法】2. 新建实体类对应的公共接口 PluginProvider.java
public interface PluginProvider { String getFeature(); void setFeature(String feature); }

Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 3. 宿主和插件都依赖该库,修改插件实体类继承自PluginProvider
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image
  1. 重新打包插件apk,更新到assets目录下替换之前的插件
5. 然后宿主调用插件方式做一下改变,只需要强转为PluginProvider即可,不依赖于插件具体的实体类类型
///< 面向接口编程调用插件代码 PluginProvider pluginProvider = (PluginProvider) mClass.newInstance(); pluginProvider.setFeature("不帅么?"); ///< 然后本地进行一些提示等操作 Toast.makeText(this, pluginProvider.getFeature(), Toast.LENGTH_SHORT).show();

Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 然后就ojbk了。
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image **C. **有时候我们希望通过回调的方式调用插件的方法,因为插件还要做很多事情才能回调给宿主(比如插件需要去下载皮肤主题资源,然后解压校验,成功后才能通知宿主进行相关设置),此时我们就采用接口编程回调的方式实现。回调我们经常用啦,问题不大哈...
1. 公共插件库中我们定义一个回调接口,并提供一个invokeCallBack(ICallBack callBack)方法. IDynamic.java
public interface IDynamic { void invokeCallBack(ICallBack callBack); }

ICallBack.java
public interface ICallBack { void callback(PluginProvider pluginProvider); }

PluginProvider.java
public interface PluginProvider { String getFeature(); void setFeature(String feature); }

2. 然后插件模块就可以新建一个Dynamic 继承实现IDynamic的方法,给出回调(利用线程做一个模拟)
import popeeee.hl.com.pluginlibrary.ICallBack; import popeeee.hl.com.pluginlibrary.IDynamic; public class Dynamic implements IDynamic { @Override public void invokeCallBack(final ICallBack callBack) { ///< 操作获取某些信息,然后回调给宿主 new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } PluginTest pluginTest = new PluginTest(); pluginTest.setFeature("我来自互联网,我标志了人类的一大进步!“呸,不要脸!"); callBack.callback(pluginTest); } }).start(); } }

3. 然后宿主app此时不再加载对应的实体类(因为你加载了实体类也只是自己设置,自己获取信息,没什么卵用!)。 此时我们加载Dynamic类,然后调用插件的invoke方法来请求网络等操作获取我们真实想要的数据....
记得重新打包plugin模块的apk,更新下下
然后修改下加载实体类并且进行强制转换
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image
/** * 默认hello world文本框添加点击事件 android:onClick="CallPlugin" * @param view */ public void CallPlugin(View view) { try { ///< 加载插件的类(插件的包名.类名) Class mClass = dexClassLoader.loadClass("popeeee.hl.com.plugin.Dynamic"); /// 1\. 反射方式调用 /////< 获取类的实例 //Object beanObject = mClass.newInstance(); // /////< 然后通过反射获取对应的方法 //Method setFeatureMethod = mClass.getMethod("setFeature", String.class); //setFeatureMethod.setAccessible(true); //Method getFeatureMethod = mClass.getMethod("getFeature"); //getFeatureMethod.setAccessible(true); // /////< 然后执行对应方法进行相关设置和获取 //setFeatureMethod.invoke(beanObject, "丑的不行呀!"); //String feature = (String) getFeatureMethod.invoke(beanObject); /////< 然后本地进行一些提示等操作 //Toast.makeText(this, feature, Toast.LENGTH_SHORT).show(); ///// 2\. 强制转换对应包含操作方法的对象 //PluginTest pluginTest = (PluginTest) mClass.newInstance(); //pluginTest.setFeature("丑的还可以呀2!"); // /////< 然后本地进行一些提示等操作 //Toast.makeText(this, pluginTest.getFeature(), Toast.LENGTH_SHORT).show(); /////< 面向接口编程调用插件代码 //PluginProvider pluginProvider = (PluginProvider) mClass.newInstance(); //pluginProvider.setFeature("不帅么?"); // /////< 然后本地进行一些提示等操作 //Toast.makeText(this, pluginProvider.getFeature(), Toast.LENGTH_SHORT).show(); ///< 面向切面编程调用插件代码 IDynamic iDynamic = (IDynamic) mClass.newInstance(); iDynamic.invokeCallBack(new ICallBack() { @Override public void callback(PluginProvider pluginProvider) { Looper.prepare(); ///< 然后本地进行一些提示等操作 Toast.makeText(MainActivity.this, pluginProvider.getFeature(), Toast.LENGTH_SHORT).show(); Looper.loop(); } }); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } }

这样就可以了
Android-插件化技术之我也来入个门-DexClassLoader加载apk|Android-插件化技术之我也来入个门-DexClassLoader加载apk,反射调用插件方法
文章图片
image 到这里插件的入门算是有所了解,另外自己亲自实践了一把,感觉还是不一样的。另外还有插件的两个入门点,一个是插件资源的加载,一个是插件的Activity的加载启动。这个两个小萌新要后面再搞。
搞的前提:1. 小萌新要去了解资源加载相关的机制,原理,源码的解读 2. 同样Activity的加载也是需要解读一些源码方可深入些。 另外如果对ClassLoader还在陌生的话,有必要去看下官方api,做一个解读了....
Demo下载地址还是贴下吧,万一需要了 https://gitee.com/heyclock/doc/blob/master/PluginTest/PluginTest.zip
先到这,贴几个我觉得不错的文章,共勉之,一起加油, 很多东西还是要自己实践...还得有自己理解!
Android插件化技术入门
Android插件化入门指南
Android插件化——资源加载
https://blog.csdn.net/liangfeng093/article/details/78120803

    推荐阅读