简介
插件化技术最初源于免安装运行 apk 的想法,这个免安装的 apk 就可以理解为 插件,而支持插件的 app 我们一般称之为 宿主。
插件化解决的问题
APP的功能模块越来越多,体积越来越大
模块之间的耦合度高,协同开发沟通成本越来越大
方法数目可能超过65535,APP占用的内存过大
应用之间的互相调用
插件化 Vs 组件化
组件化开发就是将一个app分成多个模块,每个模块都是一个组件,开发的过程中,我们可以让这些组件相互依赖,或者单独调式部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
插件化开发和组件化略有不同,插件化开发是将整个app拆分成多个模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk,最终打包的时候宿主apk和插件apk分开打包。
各大插件化技术对比
特性 | dynamic-load-apk | DynamicAPK | Small | Replugin | VirtualAPK |
---|---|---|---|---|---|
作者 | 任玉刚 | 携程 | wequick | 360 | 滴滴 |
支持四大组件 | 只支持Activity | 只支持Activity | 只支持Activity | 全支持 | 全支持 |
组建无需在宿主manifest中预注册 | √ | × | √ | √ | √ |
插件可以依赖宿主 | √ | √ | √ | × | √ |
支持pendingIntent | × | × | × | √ | √ |
Android特性支持 | 大部分 | 大部分 | 大部分 | 几乎全部 | 几乎全部 |
兼容性适配 | 一般 | 一般 | 中等 | 高 | 高 |
插件构建 | 无 | 部署aapt | Gradle插件 | 无 | Gradle插件 |
在选择上述几款插件化开发框架时,我们最好根据自身的需求来评定,如果加载的插件跟宿主没有任何关系,也不需要和宿主进行通信,比如加载第三方 App,那么推荐使用 Replugin,其他情形下,比较推荐使用 VirtualAPK。
插件化实现思路
那么该如何实现一个插件化功能呢?
首先我们面临要解决的问题是如何加载插件apk,加载完成之后就是要处理安装插件apk。而一个apk其实就是由 代码 和 资源 组成。
基于此,处理插件的两个问题即变为:如何加载插件的类,如何加载插件的资源,这之后再去解决的问题:如何调用插件类。
如何加载插件的类
类加载机制原理
首先要知道,一个完整的 Java 程序是由多个 .class 文件组成的,在程序运行过程中,需要将这些 .class 文件 加载到 JVM 中才可以使用。而在 Android 中,这些 .class 文件会被合并优化成一个或者多个的 classes.dex 文件,这是构成apk文件的其中之一的文件,而负责加载这些 .class 文件的 就是接下来要分析的类加载器(ClassLoader)。
类加载(ClassLoader)
在 Java 中,JVM 加载的是 class 文件,而在 Android 中,DVM 和 ART 加载的则是 dex 文件,虽然两者都是用的 ClassLoader 加载,但是因为加载的文件类型不同,因此还是有区别的。有关 Java 的类加载,请移步此篇 Java进阶之深入理解ClassLoader,这里着重介绍 Android的 ClassLoader 是如何加载 dex 文件。
ClassLoader 是一个抽象类,实现类主要分为两种类型:系统类加载器 和 自定义加载器。
其中系统类加载器主要包含三种:
BootClassLoader
用于加载 Android Framework 层 class 文件
PathClassLoader
用于 Android 应用程序类加载器,可以加载指定的 dex、以及 jar、zip、apk 中的classes.dex 文件
DexClassLoader
用于加载指定的 dex、以及 jar、zip、apk 中的classes.dex 文件
类继承关系图如下:
先看 PathClassLoader 和 DexClassLoader(Android Framework源码是基于9)
// API 28 /libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java |
根据源码可知,PathClassLoader 和 DexClassLoader 都是继承自 BaseDexClassLoader,且类中只有构造方法,它们的类加载逻辑全部由 BaseDexClassLoader 中。
这里需要注意的是,在8.0之前,二者的唯一区别是第二个参数 optimizedDirectory,这个参数代表的是 生成的 odex(优化后的dex)存放的路径,PathDexLoader 直接为 null,而 DexClassLoader 使用的则是用户传递进来的路径,而在8.0之后,二者就完全一样了。
加载原理
使用类加载器加载类 代码如下:DexClassLoader dexClassLoader = new DexClassLoader(dexPath,
context.getCacheDir().getAbsolutePath(), null, context.getClassLoader());
try {
Class<?> clazz = dexClassLoader.loadClass("com.tufusi.kkplugin.XXX");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
因为需要将插件的 dex 文件加载到宿主里面,所以接下来分析 DexClassLoader 类加载器源码,理解其是如何加载一个 apk 的 dex 文件的。
但是我们在 DexClassLoader 及其父类 BaseDexClassLoader 均没有找到相关加载类的方法,只能继续向上查找,最终在 ClassLoader 类中找到 loadClass 方法,源码如下(源码是 API 28 Android 9.0):
// /libcore/ojluni/src/main/java/java/lang/ClassLoader.java |
类加载流程:首先检测这个类是否已经被加载了,如果已加载,则直接获取并返回;如果没有加载,parent不为null,则调用parent的loadClass方法进行加载,这样依次递归,如果找到了或者加载了就返回,如果没找到也加载不了,才自己去加载。整个过程就是常说的双亲委托机制。
双亲委派模式
所谓双亲委派模式,当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器去加载,也就是说,只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程。