Android学习之热修复总结

做好计划,定期复盘,感知责任,提高执行力

一、什么是热修复

在应用上线后出现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...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容