一、概述
以前对Android 的热修复方案有一些了解,知道几个有名的开源方案,原理大概理解,但是没有整理汇总一下,上周听了玉斌大哥在公司做的分享后,感受颇多觉得写篇博客记一下,不能浪费。
热修复是指在不发新版的情况下修复线上的紧急 bug,长久以来做移动开发的人员都羡慕做后端或者做 web 前端的人员可以随时发布来修复 bug。那么 移动开发有没有这样的方案呢?
Hybrid
如:PhoneGap、国内的 AppCan。本质还是利用了 Web 开发在更新上的优势,但体验不如 Native。
ReactNative
像Web一样可以随时发布,又有原生的体验,所以这个方案优势挺大,有很多项目都在尝试。
Native HotFix
可以不用发新版本,直接下发补丁包修复 bug,纯原生体验。
iOS有JSPatch、Android 有 AndFix、Nuwa、DroidFix 、HotFix等
二、Android Native HotFix
下面按照原理分类为
(一) Dex分包方案(QZone的方案)(如 Nuwa、DroidFix、HotFix、RocooFix)
原理:如果多个 dex 里有相同的class,那么虚拟机会优先使用最先加载的 dex 中的 class。所以将有问题的类打包到一个新dex(pathc.dex),然后下发给 app,app 启动时将该patch.dex插入到其他 dex 前面优先加载。就达到了 替换有问题 class 的目的。
这里有两个难点:
1. 改变dex加载顺序
2. class_ispreverified
错误信息:java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
原因
dex转化成odex时会执行dvmVerifyClass进行类的校验,如 B的引用和B都在一个 dex 里,则 B 会被打上class_ispreverified标记,但是我们优先加载 A’,A’和B在两个不同的 dex,但 B 却打上了class_ispreverified标识所以就报错了。
两个相关联的类在不同的dex中并且有class_ispreverified就会报错,谷歌MultiDex拆分dex的时候就会处理这种情况如果A 和 B 不在同一个 Dex 就不会给 B标记class_ispreverified。
但我们是热修复,patch.dex 是事后打的 dex,class.dex已经安装的用户手机里了,所以我们无法修改 B 的标识。
解决方案:
我们发布版本打包时,将所有的类都引用一个单独dex的一个类,这个所有的类都不会标记class_ispreverified了,当然这也就失去了class_ispreverified的意义,会有性能上的影响。
更新:还有一个巧妙的方案来防止class_ispreverified,就是在工程里放入一个android.jar 已经存在的类,这样会导致multiple class declarations 从而不会再对class 校验。比如使用com.android.internal.util.Predicate,这样类的特点是特别简单没有额外引用,然后它从android api 8 就存在了。
QZone 安卓App热补丁动态修复技术介绍
深度理解Android InstantRun原理以及源码分析
(二)smali diff方案(阿里的方案)(以 AndFix为例)
1. 原理
2. 方法替换(AOP)
搞过 Android Aop 的同学知道,我们可以在执行一个方法的前插入另一个方法,运用这个思路,我们可以把有 bug 的方法替换成我们下发的新方法。
下边是 AndFix 使用 JNI hook 方法的一段代码,供大家理解。另一个实现AOP 的开源方案
dexposed
Android AOP 常用有的方法有 JNI HOOK 和 静态织入。
常用的静态织入的库有:
1、APT 是代码生成,java文件 生成 java文件。使用的annotation类型是SOURCE.
2、Aspectj是静态织入。静态织入:指在编译时期就织入,即:编译出来的class文件,字节码就已经被织入了。AspectJ就是一个编译器,把java文件编译成想要的class文件。使用的annotation类型是SOURCE.
3、ASM或者Javassist或者dexmaker 是Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。使用的annotation类型是class.
注:
cglib底层采用ASM字节码生成框架,实现动态代理实现AOP
cglib在android中是不能使用的。使用dexmaker框架来仿照动态生成.dex文件,实现cglib的动态代理功能
3. ApkPatch tool
AndFix 提供了一个打包 patch 的工具,这个工具将旧Apk和新Apk做 diff 得出一个patch apk。
熟悉增量更新的同学应该知道,增量更新也是做 diff 求增量包,但是他是在字节码层面上求增量包,增量包+旧 apk 合并成新 apk。
而ApkPatch是通过对比两个 apk 反编译后的 smali 文本文件来求 diff 的。所以他是从 class 层面上求 diff。得到的patch就是标准的 dex 包。 该 dex 可直接被 load。
4. 以前注意事项
一个 patch 包要修复哪些类的哪些方法,这个路径信息会被打包到 patch.apk 的META-INFO里
patch.apk在打包时要使用相同的签名。
混淆的情况:打包 patch.apk 时要使用主apk混淆时导出的 mapping 文件,防止主apk和patch.apk 由于混淆而无法对应相应的类和函数。
目前 apkpatch 没有开源,还有一些小 bug,如不支持 mulitdex。虽然没有开源但还是有方法修复的,比如反编译导出源码、还有 javasist 神器。具体怎么修复以后再说,也可能官方会修复。
(三)微信的方案
AndFix 由于影响正向开发过程(只能修改方法、不能修改 field、不能新增类等问题)、库本身难于维护以及发现的莫名其妙的 bug(不同rom对dex的校验机制不一致)。
nuwa 仅支持更新 Java 代码,不能更新资源和 so 文件。
全量替换新的Dex。即我们完全使用了新的Dex,那样既不出现Art地址错乱的问题,在Dalvik也无须插桩。当然考虑到补丁包的体积,我们不能直接将新的Dex放在里面。但我们可以将新旧两个Dex的差异放到补丁包中,最简单我们可以采用BsDiff算法。
附《Android核心知识笔记2020》分享
前段时间我和圈子里的几位架构师朋友一起闲聊时的突发奇想,我们在学习Android开发的时候或多或少也受到了一些前辈的指导,所以想把这份情怀延续下去。三个月后,这套资料就出来了,需要这份资料的朋友加Android学习交流群1049273031即可获取。