携程DynamicAPK插件化框架源码分析
Author:莫川
插件核心思想
1.aapt的改造
分别对不同的插件项目分配不同的packageId,然后对各个插件的资源进行编译,生成R文件,然后与宿主项目的R文件进行id的合并。
要求:由于最终会将所有的资源文件id进行合并,因此,所有的资源名称均不能相同。
2.运行ClassLoader加载各Bundle
和MultiDex的思路是一样的,所有的插件都被加载到同一个ClassLoader当中,因此,不同插件中的Class必须保持包名和类名的唯一。否则,加载过的类不会再次被加载。
优缺点:各个Bundle之间完全可以相互调用,但是这也造成了各个Bundle之间ClassLoader的非隔离性。并且随着数组的加长,每次findClass的时间会变长,对性能照成一定长度的影响。
让我们在熟悉一下这张图:
在DynamicAPK框架中,每个Bundle被加载到ClassLoader的调用栈如下:
Bundle的Application:BundleBaseApplication
->BundleBaseApplication(onCreate)
->BundleCore(run)
->BundleImpl(optDexFile)
->BundleArchiveRevision(optDexFile)
->BundlePathLoader(installBundleDexs)
->…
如下图所示:
3.热修复
由于所有的插件都被加载到同一个ClassLoader当中,因为,热修复的方案都是从dexElements数组的顺序入手,修改expandFieldArray方法的实现,将修复的类放到dexElements的前方。核心代码如下(详见BundlePathLoader):
private static void expandFieldArray(Object instance, String fieldName,
Object[] extraElements,boolean isHotFix) throws NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
synchronized (BundlePathLoader.class) {
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(
original.getClass().getComponentType(), original.length + extraElements.length);
if(isHotFix) {
System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);
}else {
System.arraycopy(original, 0, combined, 0, original.length);
System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
}
jlrField.set(instance, combined);
}
}
调用的关键代码如下(HotPatchItem.class):
public void optDexFile()