美文网首页
热修复研究笔记

热修复研究笔记

作者: 浪里_个郎 | 来源:发表于2020-04-30 15:21 被阅读0次

    热修复原理

    目前了解的热修复方案有:
    从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的类已经没有机会被加载了

    ClassLoader类图

    从上图可以看到,要修改的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的结构。稳定性大大提高!

    参考:
    热修复——深入浅出原理与实现
    干货满满,Android热修复方案介绍
    Android热修复技术原理详解

    相关文章

      网友评论

          本文标题:热修复研究笔记

          本文链接:https://www.haomeiwen.com/subject/khdbwhtx.html