热修复框架种类和对比
1.资源修复有2种方案
a.instantRun方案
丶通过反射创建一个新的assetManager
丶通过反射调用addAssetPath方法加载外部的资源
丶通过反射修复每一个activity的Resource中的mAssets字段(所有引用到地方都改)
b.SoPhix方案
这个是 Sophix 采用的方案,原理是构造一个 package id 为 0x66 的资源包,只含有改变的资源,将其直接添加到原有的 AssetManager 中,这样不会与原来的 package id 0x7f 冲突。然后将原来的 AssetManager 重新进行初始化即可,就不需要进行繁琐的反射替换操作了。
2. 代码修复有3种方案
a.类加载方案,看下DexPathList的findclass方法
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
一个element实际就代表一个dex文件,找一个类,是从第一个dex对应的element里面的dexfile去找
从上面的代码就得到一种方案,用PathClassloader或者DexClassloader加载一个外部dex
然后把他的element放在原本的elements之前,这样以后就会加载新的dex中的类了。
类加载方案是需要重启App的,因为类无法卸载,要重新加载类,就必须重启app
虽然很多热修复框架都是采用类加载方案,但是还是有一些区别。
a.QQ空间超级补丁和Nuwa是上面的降补丁包放在elements数组第一个元素
b.微信tinker是新旧apk做diff,得到path.dex再与手机原本的dex合并,生成新的class.dex,然后再反射将此dex放在第一个元素
c.饿了么的Amigo是将每一个element取出来,组成新的element数组,运行时再用新的element数组替换现有的数组
b.底层替换方案,nativehook
底层替换方法,不会从新加载类,限制较多,不能增减原有类的方法和字段,是及时生效的
原理是直接在native层替换方法入口,或者整个方法体
c.instantRun方案
原理是每个类都增加一个静态变量,在每个方法执行前,判断此变量是否发生变化,变化了执行新的类中方法
参考这种方法的有美团Robust和Aceso
动态链接库修复
1.替换System.loadLibrary (tinker方式)
在加载 so 库的时候,系统提供了两个接口
System.loadLibrary(String libName):用来加载已经安装的 apk 中的 so
System.load(String pathName):可以加载自定义路径下的 so
通过上面两个方法,我们可以想到,如果有补丁 so 下发,我们就调用 System.load 去加载,如果没有补丁 so 没有下发,那么还是调用 System.loadLibrary 去加载系统目录下的 so,原理比较简单,但是我们需要再上面进行一层封装,并对调用 System.loadLibrary 的地方都进行替换。
2.修改nativeLibraryPathElments的值
与类加载修复一样,获取新的so路径,通过反射将它设置在此elements之前,当找到path之后就会return。不会去找后面的path了。
DexPathList的findLibrary方法
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (NativeLibraryElement element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);
if (path != null) {
return path;
}
}
return null;
}
可以参考部分以下代码
Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
Object dexPathList = pathListField.get(classLoader);
Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
nativeLibraryPathElements.setAccessible(true);
nativeLibraryPathElements.set(dexPathList, nnnnnewelements);