在VirtualApk官方文档https://github.com/didi/VirtualAPK/wiki/第三方技术文章中介绍了加载插件四大组件和资源的原理, 我想总结一下加载插件的原理并画了个流程图。
为了加深对VirtualApk的理解, 考虑问题:
1、插件中的类是被哪个classloader加载的? 答:跟Constants.COMBINE_CLASSLOADER参数有关。
2、插件能加载宿主的类是如何实现的? 答:将宿主的dexElements插入到插件DexClassLoader的dexElements前面。
3、插件之间能相互加载对方的类吗? 答:不能;
4、如果宿主和插件有相同的类, 到底加载的是哪一个?答:跟Constants.COMBINE_CLASSLOADER参数有关。
5、插件能加载其它插件吗? 答:不能;
6、各个插件的包名能相同吗? 答:不能;
7、如何加载插件中类? 答:详见PluginContext类, 在启动插件中activity时会替换上下文, 包括mResources、mBase、mApplication等字段,从而可以使用插件classloader加载其中的类。
8、Class.forName和new SomeClass()的区别是forName只加载当前类和静态成员, 而new对象则加载当前类、静态成员和成员变量类。
以VirtualApk里的demo为例,按照流程图的顺序依次讲解:
1、 下图是完整的加载插件逻辑, 只能在宿主app执行!
2、 下面看看loadPlugin函数做了什么。 每个apk文件最后都会生成一个LoadedPlugin实例,并缓存到mPlugins里。 注意:VirtualApk只能加载apk格式的插件,不能直接加载dex;各个插件的包名必须不同。
3、下面看看create函数做了什么, 就是new个实例, 业务逻辑封装在构造函数里。
public static LoadedPlugin create(PluginManager pluginManager, Context host, File apk) throws Exception {
return new LoadedPlugin(pluginManager, host, apk);
}
4、下面仔细分析LoadedPlugin构造函数。 缓存了宿主中PluginManager实例的引用和宿主上下文, 解析插件文件(注意必须是apk格式!)并加载资源文件和dex,解析插件里的AndroidManifest.xml并缓存activity、instrument、service、provider信息,注册静态广播, 加载资源文件原理见VirtualAPK 资源加载机制分析
5、 下面着重分析一下mClassLoader是怎么生成的。 使用DexClassLoader加载插件中的dex, 然后判断布尔值Constants.COMBINE_CLASSLODER,默认值是true即可以加载宿主中的类, 值是false是不能加载宿主中的类。
如何实现的呢? 关键是insertDex函数。
6、 调试insertDex函数, 可以看到将宿主dexElements插入到插件dexElements前面, 因为ClassLoader的双亲委派机制, 会按照dexElements数组顺序依次查找加载类。 如果在前面的dexElement里成功加载了一个类, 就不会尝试去后面的dexElement里查找了。
PS: 宿主和插件有同一个类(包名、类名相同), 如果COMBINE_CLASSLOADER为true则插件会加载宿主中的类;如果值为false则会加载插件中的类。
7、加载插件的最后一步是实例化插件中的Application对象,并调用其onCreate函数。 即plugin.invokeApplicaiton(); 注意用的是this.mClassLoader, 即当前LoadedPlugin的classloader。
8、 加载完插件后启动其中的activity, 如何使用插件中的classloader和资源呢? 首先在启动插件中activity时最终走到newActivity函数, 使用插件plugin.getClassLoader()加载对应类。
在调用onCreate函数前判断是否为插件中的activity, 如果属于插件则替换mResources、mBase、mApplication, 即切换到插件上下文。 该插件activity访问资源、加载类时都使用插件LoadedPlugin的属性; 如果不属于插件即宿主activity, 则按照Android默认逻辑;
好了, 以上就是VirtualApk加载插件的流程原理。 如有遗漏,欢迎补充。
PS: 在调试宿主apk时, 不要混淆插件代码, 否则找不到插件中的函数。 发布版本时,宿主、插件都要用release版本。