选自:
继插件化后,热补丁技术在2015年开始爆发,目前已经是非常热门的Android开发技术。其中比较著名的有淘宝的Dexposed、支付宝的AndFix以及QZone的classloader超级热补丁方案。
热补丁:让应用能够在无需重新安装的情况实现更新,帮助应用快速建立动态修复能力
从上面的定义来看,热补丁节省Android大量应用市场发布的时间。同时用户也无需重新安装,只要上线就能无感知的更新。看起来很美好,这是否可以意味我们可以尽量使用补丁来代替发布呢?事实上,热补丁技术当前依然存在它的局限性,主要表现在以下几点:
热补丁技术也可以理解为一个动态修改代码与资源的通道,它适合于修改量较少的情况。以微信的多次发布为例,补丁大小均在300K以内,它相对于传统的发布有着很大的优势。
Tables | 普通升级 | 补丁升级 |
---|---|---|
数据大小 | 32.5M | 105K |
更新速度(0-50%) | 10天 | 1天(70%) |
自动升级 | wifi | 移动 |
以Android用户的升级习惯,即使是相对活跃的微信也需要10天以上的时间去覆盖50%的用户。使用补丁技术,我们能做到1天覆盖70%以上。这也是基于补丁体积较小,可以直接使用移动网络下载更新。
正因如此,补丁技术非常适合使用在灰度阶段。在过去,我们需要在正式发布前保证所有严重的问题都已经得到修复,这通常需要我们经过三次以上的灰度过程,而且无法快速的验证这些问题在同一批用户的修复效果。利用热补丁技术,我们可以快速对同一批用户验证修复效果,这大大缩短了我们的发布流程。
若发布版本出现问题或紧急漏洞,传统方式需要单独灰度验证修改,然后重新发布新的版本。利用补丁技术,我们只需要先上线小部分用户验证修改的效果,最后再全量上线即可。但是此种发布对线上用户影响较大, 我们需要谨慎而为。本着对用户负责的态度,发布补丁等同于发布版本,它也应该严格执行完整的测试与上线流程。
总的来说,补丁技术可以降低开发成本,缩短开发周期,实现轻量而快速的升级。
一入Android深似海,Android开发的另外一个痛是机型的碎片化。我们也许都会遇到”本地不复现”,”日志查不出”,”联系用户不鸟你”的烦恼。所以补丁机制非常适合使用在远端调试上。即我们需要具备只特定用户发送补丁的能力,这对我们查找问题非常有帮助。
数据统计在微信中也占据着非常重要的位置,我们也非常希望将热补丁与数据统计结合的更好。事实上,热补丁无论在普通的数据统计还是ABTest都有着非常大的优势。例如若我想对同一批用户做两种test, 传统方式无法让这批用户去安装两个版本。使用补丁技术,我们可以方便的对同一批用户不停的更换补丁。
在数据统计之路,如何与补丁技术结合的更好,更加精准的控制样本人数与比例,这也是微信当前努力发展的一个方向。
事实上,Android官方也使用热补丁技术实现Instant Run。它分为Hot Swap、Warm Swap与Cold Swap三种方式,大家可以参考英文介绍,也可以看参考文章中的翻译稿。最新的Instant App应该也是采用类似的原理,但是Google Play是不允许下发代码的,这个海外App需要注意一下。
QZone方案并没有开源,但在github上的Nuwa采用了相同的方式。这个方案使用classloader的方式,能实现更加友好的类替换。而且这与我们加载Multidex的做法相似,能基本保证稳定性与兼容性
本方案为了解决unexpected DEX problem异常而采用插桩的方式,从而规避问题的出现。事实上,Android系统的这些检查规则是非常有意义的,这会导致QZone方案在Dalvik与Art都会产生一些问题。
若采用插桩导致所有类都非preverify,这导致verify与optimize操作会在加载类时触发。这会有一定的性能损耗,微信分别采用插桩与不插桩两种方式做过两种测试,一是连续加载700个50行左右的类,一是统计微信整个启动完成的耗时。
平均每个类verify+optimize(跟类的大小有关系)的耗时并不长,而且这个耗时每个类只有一次。但由于启动时会加载大量的类,在这个情况影响还是比较大的。
这里是因为在dex2oat时fast*已经将类能确定的各个地址写死。如果运行时补丁包的地址出现改变,原始类去调用时就会出现地址错乱。这里说的可能不够详细,事实上微信当时为了查清这两个问题,也花费了一定的时间将Dalvik跟Art的流程基本搞透。若大家对这里感兴趣,后续在单独的文章详细论述。
总的来说,Qzone方案好处在于开发透明,简单,这一套方案目前的应用成功率也是最高的,但在补丁包大小与性能损耗上有一定的局限性。特别是无论我们是否真正应用补丁,都会因为插桩导致对程序运行时的性能产生影响。微信对于性能要求较高,所以我们也没有采用这套方案。
https://github.com/alibaba/AndFix
AndFix采用native hook的方式,这套方案直接使用dalvik_replaceMethod替换class中方法的实现。由于它并没有整体替换class, 而field在class中的相对地址在class加载时已确定,所以AndFix无法支持新增或者删除filed的情况(通过替换init与clinit只可以修改field的数值)。
也正因如此,Andfix可以支持的补丁场景相对有限,仅仅可以使用它来修复特定问题。结合之前的发布流程,我们更希望补丁对开发者是不感知的,即他不需要清楚这个修改是对补丁版本还是正式发布版本(事实上我们也是使用git分支管理+cherry-pick方式)。另一方面,使用native替换将会面临比较复杂的兼容性问题。
好处 | 坏处 | Cool |
---|---|---|
立即生效 | 兼容性不佳 | $ |
补丁较小 | 开发不透明 |
相比其他方案,AndFix的最大优点在于立即生效。事实上,AndFix的实现与Instant Run的热插拔有点类似,但是由于使用场景的限制,微信在最初期已排除使用这一方案。
https://github.com/alibaba/dexposed
Dexposed不支持Art模式(5.0+),且写补丁有点困难,需要反射写混淆后的代码,粒度太细,要替换的方法多的话,工作量会比较大。 暂不多加研究,带解决该问题后详细查看
http://blog.csdn.net/tencent_bugly/article/details/51821722
AndFix支持2.3-6.0,但是不清楚是否有一些机型的坑在里面,毕竟jni层不像java曾一样标准,从实现来说,方法类似Dexposed,都是通过jni来替换方法,但是实现上更简洁直接,应用patch不需要重启。但由于从实现上直接跳过了类初始化,设置为初始化完毕,所以像是静态函数、静态成员、构造函数都会出现问题,复杂点的类Class.forname很可能直接就会挂掉。
ClassLoader方案支持2.3-6.0,会对启动速度略微有影响,只能在下一次应用启动时生效,在空间中已经有了较长时间的线上应用,如果可以接受在下次启动才应用补丁,是很好的选择。
总的来说:
参考文章