热修复原理
目前了解的热修复方案有:
从java加载机制入手:腾讯tinker。
从底层二进制入手(c语言):阿里DeXposed、andfix。
物理修改dex文件加载顺序:大众点评Nuwa
博采众长:阿里Sophix
1,java加载机制实现热修复
双亲委托机制图示1,双亲委托机制保证了类只被加载一次
2,DexClassLoader查找类时,会对之前构造好dexElements数组集合进行遍历,一旦找到类名与name相同的类时,就直接返回这个class,找不到则返回null
3,我们可以在修复完bug之后,可以将这些个类打包成一个补丁文件,然后通过这个补丁文件封装出一个Element对象,并且将这个Element对象插到原有dexElements数组的最前端,这样当DexClassLoader去加载类时,优先会从我们插入的这个Element中找到相应的类,虽然那个有bug的类还存在于数组中后面的Element中,但由于双亲加载机制的特点,这个有bug的类已经没有机会被加载了
从上图可以看到,要修改的dexElements,就在加载dex的ClassLoader中。DexClassLoader和PathClassLoader源代码位于AOSP目录的libcore\dalvik\src\main\java\dalvik\system。
PathClassLoader只会加载已安装包中的dex文件,而DexClassLoader不仅仅可以加载dex文件,还可以加载jar、apk、zip文件中的dex。
dex插桩方案实现热修复的代码实现步骤:
1,修改源代码,编译出.class文件 ->
2,连带包文件夹,通过dx命令打包成dex补丁文件。如果要放入jar、apk或zip压缩包,其中的补丁dex必须命名为classes.dex。 ->
3,待热修复的设备下载补丁 ->
4,获取加载目前有问题的dex的ClassLoader ->
//加载应用程序的dex
PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
5,通过new DexClassLoader来加载所有dex补丁 ->
DexClassLoader dexLoader = new DexClassLoader(
dex.getAbsolutePath(),// 修复好的dex(补丁)所在目录
fopt.getAbsolutePath(),// 存放dex的解压目录(用于jar、zip、apk格式的补丁)
null,// 加载dex时需要的库
pathLoader// 父类加载器
6,新dex和之前的dex合并dexElements,并赋值给老类加载器的dexElements
//反射得到新老类加载器中的pathList对象
Object dexPathList = getField(dexLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
Object pathList = getField(pathLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
//反射得到pathList中的dexElements
Object arrayLhs = getField(dexPathList, dexPathList.getClass(), "dexElements");
Object arrayRhs = getField(pathList, pathList.getClass(), "dexElements");
//合并dexElements
Class<?> componentType = arrayLhs.getClass().getComponentType();
Object dexElements = Array.newInstance(componentType, k);// 创建一个类型为componentType,长度为k的新数组
//i,j指代数组长度。新创建的数组,将新老DexElements的数据都拷进去
System.arraycopy(arrayLhs, 0, result, 0, i);
System.arraycopy(arrayRhs, 0, result, i, j);
//反射给老对象中的属性重新赋值
Field declaredField = pathList.getClass().getDeclaredField("dexElements");
declaredField.setAccessible(dexElements);
declaredField.set(pathList, dexElements);
需要保证老的class在修复前并没有被classloader加载,或已经被GC,这样patch中的class才能被classloader正确加载。
2,Native hook实现热修复
阿里Dexposed,Native hook热修复原理原理简单来说,java层方法的字节码放到内存中后,JVM会查询这块内存,生成ClassObject,其中存放了指向所有这块内存中method的指针集合。通过修改directMethods中需要热修复的method对应的指针insns,就能够将method指向替换进去的,修复了问题的method。
缺点:
Dalvik上近乎完美,不支持ART(需要另外的实现方式),所以5.0以上不能用了;
最大挑战在于稳定性与兼容性,而且native异常排查难度更高;
由于无法增加变量与类等限制,无法做到功能发布级别;
后来阿里又发布了AndFix热修复框架,支持了ART,不过依然存在可能的兼容性问题。
3,物理修改dex文件加载顺序
将老的classes.dex重命名为classes2.dex,将patch dex命名为classes.dex,放入apk中,重启后ART会检查相应DEX文件的CRC32校验和,如果检查发现不对的话,ART就会重新从原有JAR中再度生成OAT,并且patch dex将排在队列最前面,里面的class会最先被加载,从而达到修复的作用。
缺点是必须重启。
3,主流热修复框架原理
AndFix:由补丁类的classLoader加载补丁类,在native层针对不同Android架构中的不同的ArtMethod结构调用对应的replaceMethod方法按照定义好的ArtMethod结构一一替换方法的所有信息如所属类、访问权限、代码内存地址等。
稳定性较差,会受到国内ROM厂商对ArtMethod结构更改的影响,所以这正是AndFix不支持很多机型的原因。
Sophix:由补丁类的classLoader加载补丁类,在native层直接memcpy(smeth,dmth,sizeof(ArtMethod))替换整个artMethod的结构。初始化类时会为这个类分配空间,AllocArtMethodArray会紧挨着的new出来放入art中的方法数组中。通过计算辅助类的前后两个方法的起始地址就可以计算出artMethod结构的大小了。
注:补丁类初始化时,也会分配自己的artMethod空间,拿这个修复过的新ArtMethod去替换旧ArtMethod的内容,不用管ArtMethod的结构。稳定性大大提高!
网友评论