版权声明:本文由张绍文原创文章,转载请注明出处:
文章原文连接:https://www.qcloud.com/community/article/101html
来源:腾云阁 https://www.qcloud.com/communityandroid
最近半年以来,Android热补丁技术热潮继续爆发,各大公司相继推出本身的开源框架。Tinker在最近也顺利完成了公司的审核,并不是常荣幸的成为github.com/Tencent上第一个正式公开的项目。git
回顾这半年多的历程,这是一条跪着走完,坑坑不息之路。或许只有本身真正经历过,深刻研究过, 才会真正的明白github
热补丁不是请客吃饭算法
对热补丁技术自己,仍是对使用者来讲都是如此。它并不简单,也有着本身的局限性,在使用以前咱们须要对它有所了解。我但愿经过分享微信在这历程中的思考与经验,能帮助你们更容易的决定是否在本身的项目中使用热补丁技术,以及选择什么样方案。安全
热补丁是什么以及它的应用场景介绍,你们能够参考文章微信Android热补丁实践演进之路微信
在笔者看来Android热补丁技术应该分为如下两个流派:框架
Native流派与Java流派都有着本身的优缺点,它们具体差别你们可参考上文。事实上历来都没有最好的方案,只有最适合本身的。性能
对于微信来讲,咱们但愿获得一个“高可用”的补丁框架,它应该知足如下几个条件:优化
在“高可用”这个大前提下,微信对当时存在的两个方案作了大量的研究:
在2016年3月,微信为了追寻“高可用”这个目标,决定尝试搭建本身的补丁框架——Tinker。Tinker框架的演绎并非一蹴而就,它大体分为三个阶段,每一阶段须要解决的核心问题并不相同。而Tinker v1.0的核心问题是实现符合性能要求的Dex补丁框架。
为了稳定性与兼容性,微信选择了Java流派。当前最大难点在于如何突破Qzone方案的性能问题,这时经过研究Instant Run的冷插拔与buck的exopackage给了咱们灵感。它们的思想都是全量替换新的Dex。
简单来讲,咱们经过彻底使用了新的Dex,那样既不出现Art地址错乱的问题,在Dalvik也无须插桩。固然考虑到补丁包的体积,咱们不能直接将新的Dex放在里面。但咱们能够将新旧两个Dex的差别放到补丁包中,这里咱们能够调研的方法有如下几个:
如何选择?在“高可用”的核心诉求下,性能问题也尤其重要。很是庆幸微信在当时那个节点坚定的选择了自研DexDiff算法,这过程虽然有苦有泪,但也正是有它,才有如今的Tinker。
在不断的深刻研究Dex格式后,咱们发现本身跳进了一个深坑,主要难点有如下三个:
这不只要求咱们须要研究透Dex的格式,也要把dex2opt与dex2oat的代码所有研究透。如今回想起来,这的确是一条跪着走完的路。与研究Dalvik与Art执行一致,这是经历一次次翻看源码,一次次编Rom查看日志,一次次dump内存结构换来的结果。
下面以最简单的Index区域举例:
要想将从左边序列更改为右边序列,Diff算法的核心在于如何生成最小操做序列,同时修正Index与Offset,实现增删改的功能。
对于Offset区,因为每一个Section可能有很是多的元素,这里会更加复杂。最后咱们获得最终的操做队列,为何DexDiff能够作到内存很是少?这是由于DexDiff算法是每个操做的处理,它无需一次性读入全部的数据。DexDiff的各项数据以下:
经过DexDiff算法的实现,咱们既解决了Dalvik平台的性能损耗问题,又解决了Art平台补丁包过大的问题。但这套方案的缺点在于占Rom体积比较大,微信考虑到移动设备的存储空间提高比较快,增长几十M的Rom空间这个代价能够接受。
信心满满上线后,却很快收到华为反馈的一个Crash:
并且这个Crash只在Android N上出现,在当时对咱们震动很是大,难道Android N不支持Java方式热补丁了?难道这两个月的辛苦都白费了吗?一切想象都苍白无力,只有继续去源码里面找缘由。
在以前的基础上,这一块的研究并无花太多的时间,主要是Android N的混合编译模式致使。更多的详细分析可参考文章Android N混合编译与对热补丁影响解析。
刚刚解决完Android N的问题,还在沉醉在本身的胜利的愉悦中。前线很快又传来噩耗,小米反馈开发版的一些用户在微信启动时黑屏,甚至ANR.
当时第一反应是不可能,全部的DexOpt操做都是放到单独的进程,为何只在Art平台出现?为何小米开发版用户反馈比较多?通过分析,咱们发现优化后odex文件存在有效性的检查:
这就很是好理解了,由于OTA以后系统image改变了,odex文件用到image的偏移地址极可能已经错误。对于ClassN.dex文件,在OTA升级系统已完成从新dex2oat,而补丁是动态加载的,只能在第一次执行时同步执行。
这个耗时可能高达十几秒,黑屏甚至ANR也是很是好理解。那为何只有小米用户反馈比较多呢?这也是由于小米开发版每周都会推送系统升级的缘由。
在当时那个节点上,咱们从新的审视了全量合成这一思路,再次对方案原理自己产生怀疑,它在Art平台上面带来了如下几个代价:
回想起来,Qzone方案它只把须要的类打包成补丁推送,在Art平台上可能致使补丁很大,但它确定比全量合成10M的Dex少不少不少。在此咱们提出分平台合成的想法,即在Dalvik平台合成全量Dex,在Art平台合成须要的Dex
DexDiff算法已经很是复杂,事实上要实现分平台合成并不容易。
主要难点有如下几个方面:
庆幸的是,面对困难咱们并无畏惧,最后实现了这一套方案,这也是其余全量合成方案所不能作到的:
事实上,DexDiff算法变的如此复杂,怎么样保证它的正确性呢?微信为此作了如下三件事情:
每一次DexDiff算法的更新,都须要通过以上三个Test才能够提交,这样DexDiff的这套算法已完成了整个闭环。
在实现过程,咱们还发现其余的一些问题:
Xposed等微信插件; 市面上有各类各样的微信插件,它们在微信启动前会提早加载微信中的类,这会致使两个问题:
a. Dalvik平台:出现Class ref in pre-verified class resolved to unexpected implementation的crash;
b. Art平台:出现部分类使用了旧的代码,这可能致使补丁无效,或者地址错乱的问题。
微信在这里的处理方式是若crash时发现安装了Xposed,即清除并再也不应用补丁。
经过Tinker v1,0的努力,咱们解决了Qzone方案的性能问题,获得一个符合“高可用”性能要求的补丁框架。
也许有人会质疑微信成功率为何这么低,其余方案都是99%以上。事实上,咱们的成功率计算方式是:
应用成功率= 补丁版本转化人数/基准版本安装人数
即三天后,94.1%的基础版本都成功升级到补丁版本,因为基础版本人数也是持续增加,同时可能存在基准或补丁版本用户安装了其余版本,因此本统计结果应略为偏低,但它能现实的反应补丁的线上整体覆盖状况。
事实上,采用Qzone方案,3天的成功率大约为96.3%,这里仍是有不少的优化空间。
在v1.0阶段,大部分的异常都是经过厂商反馈而来,Tinker并无解决“高可用”下最核心的稳定性与兼容性问题。咱们须要创建完整的监控与补丁回退机制,监控每个阶段的异常状况。这也是Tinker v2.0的核心任务,因为边幅问题这部份内容将放在下一篇文章。
关注Tinker,来Github给咱们star吧