转载请注明本文出自 clevergump 的博客:http://blog.csdn.net/clevergump/article/details/54782635, 谢谢!
什么是热修复技术?
通过向用户下发补丁包的方式, 让应用能够在无需重新安装的情况自动集成补丁包, 实现更新的技术.
热修复技术只适用于特定场景,无法完全代替传统的升级方式.
适用于修改量较少, 补丁包较小的情况。在补丁包极小的情况下,也可以直接使用移动网络下载更新, 这样能达到快速升级、快速验证、缩短发布流程的目的. 注意:是否需要使用用户流量,需要慎重考虑。例如: 下图是微信某次升级试验中, 两种升级方式的对比:
如果想对同一批用户做两种测试, 传统方式无法让这批用户去安装两个版本。使用热修复技术,就可以方便地对同一批用户不停地更换补丁, 然后得出不同的测试结果, 进行数据统计和对比分析. 例如: 如果想探究某次技术升级后的用户满意度或bug出现率, 就可以使用AB测试, 只给一部分用户推送升级补丁, 其他用户则依然保持原先的版本, 通过对比这两类用户各自的使用状况, 从而就可以知道这次热修复的内容是否需要继续完善或改进等. 如果发现使用了补丁包的用户的满意度提高了, 或者出现bug的概率降低了, 那么就可以将本次热修复的补丁包下发给所有用户了.
例如: Android Studio 的 Instant Run (热部署) 技术等.
开发团队 | 微信 |
---|---|
方案简介 | 使用 ClassLoader 动态加载多个dex文件,改变dex文件的加载顺序 |
优点 | 开发简单透明,成功率较高,支持Java、so、资源文件的替换和新增,补丁包较小,性能损耗较小,支持gradle。 |
缺点 | Dalvik机型Rom体积较大,补丁不能即时生效。 |
是否开源 | 是 |
技术支持情况 | 有官方技术支持QQ群 (群号: 377388954) |
GitHub主页 | https://github.com/Tencent/tinker |
开发团队 | QQ空间 |
---|---|
方案简介 | 使用 ClassLoader 动态加载多个dex文件,改变dex文件的加载顺序. |
优点 | 开发简单透明,成功率较高,支持so、资源文件的替换 |
缺点 | 补丁包较大,性能损耗较大,补丁不能即时生效,不支持新增Java文件. |
是否开源 | 否 |
技术支持情况 | 无(因为不开源) |
类似开源方案 | Nuwa, RocooFix |
开发团队 | 阿里巴巴 |
---|---|
方案简介 | 采用native hook,替换class中方法的实现. |
优点 | 补丁即时生效,性能损耗较小,Rom体积较小. |
缺点 | 开发复杂,native异常排查难度高,稳定性和兼容性不好,成功率不高,不支持新增Java文件,不支持so、资源文件的替换,不支持gradle. |
是否开源 | 是 |
技术支持情况 | 不确定是否有专门的技术支持团队 |
GitHub主页 | https://github.com/alibaba/AndFix |
开发团队 | 美团 |
---|---|
方案简介 | 使用 ClassLoader 动态加载多个dex文件,改变dex文件的加载顺序,同时又借鉴了multidex的设计. |
优点 | 成功率最高,补丁即时生效,性能损耗较小,Rom体积较小. |
缺点 | 开发复杂不透明,不支持新增Java文件,不支持so、资源文件的替换,不支持gradle. |
是否开源 | 否 (今后有可能开源) |
技术支持情况 | 无 (因为暂未开源) |
方案名称 | Tinker | QZone | AndFix | Robust |
---|---|---|---|---|
类替换 | yes | yes | no | no |
So替换 | yes | no | no | no |
资源替换 | yes | yes | no | no |
全平台支持 | yes | yes | yes | yes |
即时生效 | no | no | yes | yes |
性能损耗 | 较小 | 较大 | 较小 | 较小 |
补丁包大小 | 较小 | 较大 | 一般 | 一般 |
开发透明 | yes | yes | no | no |
复杂度 | 较低 | 较低 | 复杂 | 复杂 |
gradle支持 | yes | no | no | no |
Rom体积 | 较大 | 较小 | 较小 | 较小 |
成功率 | 较高 | 较高 | 一般 | 最高 |
是否开源 | 是 | 否 | 是 | 否 |
技术支持情况 | 有官方QQ群 | 无 | 无 | 无 |
通过前边各大方案的对比可知, 微信Tinker方案相对于其他方案具有如下优势:
所以,对于商用APP,当前推荐使用微信Tinker方案。
下面以开源框架 nuwa (https://github.com/jasonross/Nuwa) 为例,来介绍基于ClassLoader的相关热修复方案的实现原理。
基于ClassLoader实现热修复,简单来说,就是把多个dex文件塞入到 APP 的 ClassLoader之中. 如果多个dex文件中有重复的类,当用到这个重复的类的时候, 系统内部会按照一定的优先级顺序先后遍历各个dex文件, 只要在某个dex文件中找到了该类, 就会停止后续的遍历, 所以后续其他 dex中具有同名的类就不会被调用了.
假设一个 APP被分成多个dex文件, ClassLoader要从中找出 Bug.class文件进行加载, 而 dex文件的优先级是: classes1.dex > classes2.dex > classes3.dex > … 那么, ClassLoader 查找该 class文件的流程图如下:
所以, 如果一个类存在bug (假设该类名为 Bug.java), 那么修复步骤如下:
简单来说, 组成一个 APP的所有 dex文件都被存放在一个叫做 dexElements 的数组中, ClassLoader 要遍历查找某个 class文件, 其实就是从 dexElements 数组中按照数组索引从0开始逐渐增大的顺序, 一个一个地取出 dex文件进行查找, 只要找到就立即结束. 数组索引越小的 dex文件, 就越会被 ClassLoader优先遍历. 所以, 要想让一个dex文件被 ClassLoader查找的优先级最高(也即: 被 ClassLoader第一个查找), 只需将该 dex文件放在该数组的索引0位置处即可. 我们可以利用发射技术, 获取到该数组, 然后将补丁文件 patch.dex注入到该数组中的索引0位置即可.
(注意: 以上只是为了便于理解而简化了的描述, 实际情况更复杂, 需要看具体的源码)
在按照上述步骤注入 patch.dex时, 却可能会报如下错误, 为什么?
其实, 虚拟机为了性能考虑, 加载的不是 dex文件, 而是将 dex经过进一步优化后的 odex文件. 虚拟机在生成 odex文件的过程中, 会判断某个类是否引用了其他 dex文件中的代码, 如果没有, 则会为该类打上CLASS_ISPREVERIFIED 标签, 表示此后该类将一直不会引用其他 dex文件中的代码, 这其实是一种优化.
所以, 假设我们原先的 APP中有两个类: Caller类和 Bug类, 二者都在同一个 dex文件中, 并且Caller类中会调用 Bug类的代码, 而没有调用其他 dex文件中的代码, 那么当我们安装该 APP时, Caller类就会被打上CLASS_ISPREVERIFIED 标签. 此后, 我们发现 Bug类的代码存在 bug需要修复, 如果采用热修复技术, 那么就意味着 Caller类需要调用来自 patch.dex中的 Bug类, 也就是需要调用其他 dex文件中的代码, 由于 Bug类先前已被打上 CLASS_ISPREVERIFIED 标签, 所以会报错, 热修复失败. 要想继续使用热修复技术, 就必须阻止 Caller类被打上该标签. 更广泛地说, 要想避免该问题, 就必须要让原先 APP中的所有类都不能打上该标签.
其实很简单, 只需在 APP的第一个版本中就内置一个 dex文件, 让 APP中的所有类都去调用该 dex文件中的某个类即可. 这也就是业内俗称的 “插桩”技术.
例如: 可以在 APP的 assets文件夹中内置一个 hack.dex文件, 该hack.dex文件中只包含一个 Hack.class文件. 然后在该 APP的所有类中都调用如下代码:
System.out.println(Hack.class);