Tinker热修复包含多种类型的修复,如类修复,资源修复等,此处介绍类修复的基本原理。
修复原理
应用类的代码均位于apk包的dex文件中,在安卓设备上安装后dex文件一般位于/data/app/packageName-1/下的目录中,类的加载则需要依靠ClassLoader,由于有多个dex文件,加载的规则为越靠前的dex优先被系统使用。
常见的PathClassLoader继承自BaseDexClassLoader,源码地址/libcore/dakvik/src/main/java/dalvik/system/BaseDexClassLoader,该类只有一个属性
private final DexPathList pathList;
该类提供findClass方法在pathList中找期望的类,而dexElements则位于DexPathList类中。
类修复的原理是hook了ClassLoader.pathList.dexElements[],因为ClassLoader的findClass是通过dexElements中的dex来查找类
修复后dexElements如下
patch.dex - classes.dex - classes1.dex - classes2.dex
类修复的具体实现
在应用编译生成dex文件时,需要确保主包中代码没有问题,热修复相关的代码也应该在主包中完成。在模块build.gradle文件中的配置如下
android {
defaultConfig {
......
// 开启分包
multiDexEnabled true
// 配置分包细节
multiDexKeepFile file('multidex-cofig.txt')
}
dexOptions {
javaMaxHeapSize "4g"
preDexLibraries = false
additionalParameters = [
'--multi-dex', // 多dex分包
'--set-max-ids-number=50000', // 每个dex文件包含的方法数上限
'--main-dex-list=' + '/multidex-config.txt', // 打包到主包classes.dex的类列表
'--minimal-main-dex'
]
}
}
dependencies {
......
implementation 'com.android.support:multidex:1.0.3'
}
如上配置显示分包配置文件与build.gradle位于同一目录,该文件配置主包中包含的类,例如
com/example/sample/BaseApplication.class
com/example/sample/BaseActivity.class
com/example/sample/MainActivity.class
在客户端收到用于修复的patch.dex文件后,需要做如下事情
- 将patch.dex文件复制到context.getDir(“odex”, Context.MODE_PRIVATE).getAbsoluteDir()中
- 自定义一个DexClassLoader,继承自BaseDexClassLoader
- 用DexClassLoader加载包含修复代码的patch.dex文件
- 将自有的和系统的dexElements数组合并,并且设置patch的element靠前
- 通过反射将合并后的dexElements设置给系统的dexElements
其中获取补丁包的简单方式就是修复代码后再打包apk,然后解压apk包拿到其中的dex文件。修复的伪代码如下
// 伪代码
void hotfix(Context context, File dex) {
// 复制patch.dex到odex目录
FileUtils.copy(dex.getAbsolutePath(), context.getDir(“odex”, Context.MODE_PRIVATE).getAbsoluteDir() + "/patch.dex";
// 获取系统ClassLoader和自定义的ClassLoader
Object systemClassLoader = context.getClassLoader();
Object myClassLoader = new DexClassLoader(dex.getAbsolutePath(), optmizeDir, null, context.getClassLoader());
// 获取对应的dexElments然后合并
Object systemElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(systemClassLoader));
Object myElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(myClassLoader));
Object dexElements = ArrayUtils.combine(systemElements, myElements);
// 将系统ClassLoader的PathList中的dexElements设置为合并后的dexElements
ReflectUtils.setField(systemDexElementsField, dexElements);
}