美文网首页
Android之热修复总结

Android之热修复总结

作者: 大北小西 | 来源:发表于2020-12-09 11:42 被阅读0次
    做好计划,定期复盘,感知责任,提高执行力

    一、什么是热修复

    在应用上线后出现Bug需要及时修复时,不需要再发新的安装包,只要发布补丁包,在用户无感知的情况下修复Bug。

    二、怎么进行热修复

    从以下几个角度入手来描述热修复需要解决的问题:服务端、用户端、开发端。
    1、服务端:补丁包管理:
    2、用户端:进行热修复:什么时候执行热修复?怎么执行热修复(使用补丁包)?Android版本兼容问题;
    3、开发端:生成补丁包:补丁包是什么?如何生成补丁包?开启混淆后怎么处理?对比改动自动生成补丁包(Gradle)?

    三、常用热修复框架:

    热修复方案很多,其中比较出名的有腾讯的Tinker、阿里的AndFix、美团的Robust以及QZone的超级补丁方案:

    几种热修复方案的简单对比
    下面就从 类替换即时生效 两个角度来对以上几种方案做个简单说明:
    1、Tinker和QZone都采用了类Class的替换:根据类加载机制,所以就不能实现即时生效,而这两者的区别在于补丁包上,Tinker采用了差分对dex包做了diff,而Qzone则是全量的类替换;
    2、AndFix和Robust可以理解为是对方法Method的替换:AndFix是在Native动态替换java层的方法,通过nativce层hook JAVA层代码,通过注解来找到替换的Method;而Robust是对每个函数在编译打包阶段自动的插入一段代码,类似于代理,将方法执行的代码重定向到其他方法中;

    四、热修复所用到的知识点

    类加载:BootClassLoader、PathClassLoader、DexClassLoader、双亲委派机制以及类查找流程(注:此知识点会有专门章节解析),基于此可以简单总结一下热修复的流程:
    ——获取当前应用的PathClassLoader
    ——反射获取到DexPathList属性对象pathList
    ——发射修改pathList的dexElements:获取path.dex补丁包的dexElements、获取pathList的dexElement、将两个dexElements按照补丁包dexElement在前原有dexElemtns灾后的顺序合并后反射复制给pathList的dexElement;

    五、热修复兼容问题:

    1、Elements数组获取问题:补丁包path.dex最后要获取其dexElements,针对不同版本获取接口makePathElement方法名称或者参数获取会有不同,需要兼容;
    2、AndroidN混合编译问题:在使用混合模式运行时,应用在安装时不做编译,而是运行时解释字节码,同时在JIT编译了一些代码将这些代码信息记录至Profile文件,等到设备空闲的时候使用AOT编译生成app_imagebase.art(类对象映射)文件,这个art文件会在apk启动是自动加载(相当于缓存),根据类加载原理,类被加载了无法被替代,即无法修复;
    ——解决方案:运行时替换PathClassLoader:app_image中的的class是插入到PathClassLoader的ClassTable中的,假设我们完全废弃掉PathClassLoader,而采用一个新建的ClassLoader来加载后续的类,即可达到cache无用化的作用:Thread.currentThread().setContextClassLoader(classLoade r);
    3、CLASS_ISPREVERIFIED标志:在Dalvik虚拟机中,如果一个类所调用的Class全部在一个Dex文件中,则就会被打上CLASS_ISPREVERIFIED标志;比如MainActivity类中只引用了Utils类,当打包时,MainActivity和Utils都在classes.dex中,则加载时MainActivity类就会被标记为CLASS_ISPREVERIFIFD;此时在做热修复的时候,如果用补丁包的dex中的Utils类去替换有Bug的Utils类,则就会导致MainActivity引用的Utils类不在同一个dex中,所以会和之前的标志产生冲突,出现校验失败;
    ——解决方案:利用字节码插桩技术:可在每个类中添加一个特殊类的调用,这个类单独编译生成一个dex文件,此时在加载的时候就不会被打上CLASS_ISPREVERIFIED标志,进而就能校验通过,进行替换修复;
    注:字节码插桩和ASM会有专门章节解析)

    六、Gradle插件相关:

    1、插件开发的几种方式:
    ——Build Script脚本: 把插件写在buidler.gradle文件中,一般用于简单的逻辑,并且只对改builder.gradle可见;
    ——buildSrc目录:将插件源文件放在buildSrc/src/main/groovy中,仅对此项目可见;
    ——独立项目:一个独立的JAVA项目或者模块,可将文件包发布到仓库(jcenter),方便其他项目引入;
    2、插件实现:

    public class TestPlugin implements Plugin<Project> {    
          @Override
        public void apply(Project project) {
            ...
        }
    }
    

    继承 implements Plugin<Project>,实现 apply(Project project)方法;
    3、插件扩展:

    public class TestPlugin implements Plugin<Project> {    
        @Override
        public void apply(Project project) {
            
            project.getExtensions().create("patch", PatchExtension.class);      
            project.afterEvaluate(new Action<Project>() {
              @Override
              public void execute(final Project project) {
                    ...
              });
             }
         });
        }
    }
    

    通过create方法创建扩展,patch对应扩展名,即在builder.gradle中
    对应patch,其中debugOn、applicationName则对应PatchExtension类中定义;

    apply plugin: 'com.example.patch'
    patch {
        debugOn true
        applicationName 'com.example.hotfix.MyApplication'
    }
    
    public class PatchExtension {
    
        boolean debugOn;
        String applicationName;
    }
    

    注一:插件的引入:

    apply plugin: 'com.example.patch'
    
    apply plugin: com.example.patch.plugin.TestPlugin 
    

    以上两种引入插件的方式都可以,唯一区别为一个有引号,一个没有,有引号引入的相当于插件的一个别名,如果要采用用引号引入的方式,则必须在buildSrc\build\resources\main\META-INF\gradle-plugins/com.example.patch.properties这样一个文件,别名对应于此文件的文件名,在编译时会自动填充此文件的内容:

    implementation-class=com.example.patch.plugin.PatchPlugin
    

    注二:afterEvaluate的使用:即builder.gradle也是一行一行执行的,所以一般是在脚本扫描完后的回调函数afterEvaluate中才能正确的获取patch中的配置值;

    七、利用Gradle插件自动生成补丁包

    1、Task的理解:即任务,Android工程引入的插件中,创建多个自定义任务,比如:
    —编译JAVA任务:compileDebugJavaWithJavac
    —混淆任务:transformClassesAndResourcesWithProguardFor
    —打包dex任务:transformClassesWithDexBuilderFor

    2、自动生成补丁包:
    思路:在编译生成class文件时,可以将生成的class文件的名字和对应的md5值保存,当第二次再次编译的时候可以对比生成class的文件的md5值是否有变化,如有变化,则将次class文件打入到补丁包中;
    实现:基于上述Task的理解,可以获取transformClassesWithDexBuilderFor任务的输入作为切入点,进行class文件及对应md5值的生成及校验;
    3、防止混淆开启引起补丁包异常:因为开启混淆后,可能每次编译后对同一个类混淆的名字有改变,如果有改变,会影响补丁包生成的可靠性:
    ——解决方案:可以利用开启混淆后生成的mapping文件,此文件记载了混淆前后类及方法的变化,同时可以在proguard-rules.pro中配置-applymapping选项;即在每次编译混淆后都会参照前一次混淆的名字,这样就保证了生成补丁包校验时的可靠性;

    总结一下:

    以上是基于热修复的学习的基本内容,只是简单列举了一下流程、知识点及注意点,针对每个知识点的详解后续会有文章做支持,暂时先列举出来:
    ——类加载相关...
    ——Gradle相关...
    ——热修复的Demo...

    相关文章

      网友评论

          本文标题:Android之热修复总结

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