微信Android热补丁实践演进之路

   <p><img src="https://simg.open-open.com/show/8ef01aa2289e7a9df37561b027274077.jpg"></p>    <p> </p>    <p>在了解补丁技术可以与适合做什么之后,我们回到技术本身。由于 <a href="/misc/goto?guid=4958961145161274859" rel="nofollow,noindex">Dexposed</a> 无法支持全平台,并不适合应用到商业产品中。所以这里我们只简单介绍Andfix、QZone、微信几套方案的实现,以及它们方案面临着的问题,大家也可以参考资料中的 <a href="http://www.open-open.com/lib/view/open1453255144980.html">各大热补丁方案分析和比较</a> 一文。</p>    <p> </p>    <h2><strong>一. AndFix </strong></h2>    <p><a href="/misc/goto?guid=4958968546721683981" rel="nofollow,noindex">AndFix</a> 采用native hook的方式,这套方案直接使用dalvik_replaceMethod替换class中方法的实现。由于它并没有整体替换class, 而field在class中的相对地址在class加载时已确定,所以AndFix无法支持新增或者删除filed的情况(通过替换init与clinit只可以修改field的数值)。</p>    <p><img src="https://simg.open-open.com/show/47bb3a67065ad2d31c1bf99deaf795f3.png"></p>    <p> </p>    <p>也正因如此,Andfix可以支持的补丁场景相对有限,仅仅可以使用它来修复特定问题。结合之前的发布流程,我们更希望补丁对开发者是不感知的,即他不需要清楚这个修改是对补丁版本还是正式发布版本(事实上我们也是使用git分支管理+cherry-pick方式)。另一方面,使用native替换将会面临比较复杂的兼容性问题。</p>    <p><img src="https://simg.open-open.com/show/228a7f4f7a924ece8bd955ad45f4f26c.png"></p>    <p>相比其他方案,AndFix的最大优点在于立即生效。事实上,AndFix的实现与 <a href="http://www.open-open.com/lib/view/open1462625109497.html">Instant Run的热插拔</a> 有点类似,但是由于使用场景的限制,微信在最初期已排除使用这一方案。</p>    <p> </p>    <h2><strong>二. QZone </strong></h2>    <p>QZone方案并没有开源,但在github上的 <a href="/misc/goto?guid=4958971966097492509" rel="nofollow,noindex">Nuwa</a> 采用了相同的方式。这个方案使用classloader的方式,能实现更加友好的类替换。而且这与我们加载Multidex的做法相似,能基本保证稳定性与兼容性。具体原理在这里不再细说,大家可以 <a href="http://www.open-open.com/lib/view/open1462746397044.html" rel="nofollow,noindex">参考这篇文章</a> 。</p>    <p>本方案为了解决unexpected DEX problem异常而采用插桩的方式,从而规避问题的出现。事实上,Android系统的这些检查规则是非常有意义的,这会导致QZone方案在Dalvik与Art都会产生一些问题。</p>    <p> </p>    <ul>     <li>Dalvik; 在dexopt过程,若class verify通过会写入pre-verify标志,在经过optimize之后再写入odex文件。这里的optimize主要包括inline以及quick指令优化等。</li>    </ul>    <p><img src="https://simg.open-open.com/show/39fd73503382841e54fa5a5a0c495777.png"></p>    <p> </p>    <p>若采用插桩导致所有类都非preverify,这导致verify与optimize操作会在加载类时触发。这会有一定的性能损耗,微信分别采用插桩与不插桩两种方式做过两种测试,一是连续加载700个50行左右的类,一是统计微信整个启动完成的耗时。</p>    <p> </p>    <p><img src="https://simg.open-open.com/show/5663f1c6f33f54bfacdb9bc4f607588e.png"></p>    <p> </p>    <p>平均每个类verify+optimize(跟类的大小有关系)的耗时并不长,而且这个耗时每个类只有一次。但由于启动时会加载大量的类,在这个情况影响还是比较大的。</p>    <p> </p>    <ul>     <li>Art; Art采用了新的方式,插桩对代码的执行效率并没有什么影响。但是若补丁中的类出现修改类变量或者方法,可能会导致出现内存地址错乱的问题。为了解决这个问题我们需要将修改了变量、方法以及接口的类的父类以及调用这个类的所有类都加入到补丁包中。这可能会带来补丁包大小的急剧增加。</li>    </ul>    <p><img src="https://simg.open-open.com/show/eb682be0dc669b03f137174140ce85bb.png"></p>    <p> </p>    <p>这里是因为在dex2oat时fast*已经将类能确定的各个地址写死。如果运行时补丁包的地址出现改变,原始类去调用时就会出现地址错乱。这里说的可能不够详细,事实上微信当时为了查清这两个问题,也花费了一定的时间将Dalvik跟Art的流程基本搞透。若大家对这里感兴趣,后续在单独的文章详细论述。</p>    <p> </p>    <p>总的来说,Qzone方案好处在于开发透明,简单,这一套方案目前的应用成功率也是最高的,但在补丁包大小与性能损耗上有一定的局限性。特别是无论我们是否真正应用补丁,都会因为插桩导致对程序运行时的性能产生影响。微信对于性能要求较高,所以我们也没有采用这套方案。</p>    <p> </p>    <h2><strong>三. 微信热补丁方案 </strong></h2>    <p>有没有那么一种方案,能做到开发透明,但是却没有QZone方案的缺陷呢?Instant Run的冷插拔与buck的 <a href="/misc/goto?guid=4959653417471308550" rel="nofollow,noindex">exopackage</a> 或许能给我们灵感,它们的思想都是全量替换新的Dex。即我们完全使用了新的Dex,那样既不出现Art地址错乱的问题,在Dalvik也无须插桩。当然考虑到补丁包的体积,我们不能直接将新的Dex放在里面。但我们可以将新旧两个Dex的差异放到补丁包中,最简单我们可以采用BsDiff算法。</p>    <p> </p>    <p><img src="https://simg.open-open.com/show/26eff2a369f9d0ac5f73b1d880777d56.png"></p>    <p> </p>    <p>简单来说,在编译时通过新旧两个Dex生成差异path.dex。在运行时,将差异patch.dex重新跟原始安装包的旧Dex还原为新的Dex。这个过程可能比较耗费时间与内存,所以我们是单独放在一个后台进程:patch中。为了补丁包尽量的小,微信自研了DexDiff算法,它深度利用Dex的格式来减少差异的大小。它的粒度是Dex格式的每一项,可以充分利用原本Dex的信息,而BsDiff的粒度是文件,AndFix/QZone的粒度为class。</p>    <p> </p>    <p><img src="https://simg.open-open.com/show/4255edb9a347790f2ce81faae858d1af.png"></p>    <p> </p>    <p>这块后面我希望后面用单独的文章来讲述,这里先做一个铺垫,大致的效果如下图。在最极端的情况,由于利用了原本dex的信息完全替换一个13M的Dex,我们的补丁大小也仅仅只有6.6M。</p>    <p> </p>    <p><img src="https://simg.open-open.com/show/09f6d4b83c788fa0e7733d855dfb27b8.png"></p>    <p> </p>    <p>但是这套方案并非没有缺点,它带来的问题有两个:</p>    <ul>     <li>占用Rom体积;这边大约是你修改Dex数量的1.5倍(dexopt与dex压缩成jar)的大小。</li>     <li>一个额外的合成过程;虽然我们单独放在一个进程上处理,但是合成时间的长短与内存消耗也会影响最终的成功率。</li>    </ul>    <p>微信的热补丁方案叫做Tinker,也算缅怀一下Dota中的地精修补匠,希望能做到无限刷新。</p>    <p><img src="https://simg.open-open.com/show/22de2cb3531968e41529d2b01928d701.png"></p>    <p>限于篇幅,这里对Dex、library以及资源的更多技术细节并没有详细的论述,这里希望放在后面的单独文章中。我们最后从整体比较一下这几种方案:</p>    <p> </p>    <p><img src="https://simg.open-open.com/show/29224df7135e523627f1fb30f68160d9.png"></p>    <p> </p>    <p>若不care性能损耗与补丁包大小,QZone方案是最简单且成功率最高的方案(没有单独的合成过程)。相对Tinker来说,它的占用Rom体积也更小。另一方面,QZone与Tinker的成功率大约相差3%左右。</p>    <p>事实上,一个完整的框架应该也是一个容易使用的框架。Tinker对补丁版本管理、进程管理、安全校验等都有着很好的支持。同时我们也支持gradle与命名行两种接入方式。希望在不久的将来,它可以很快的跟大家见面。</p>    <p> </p>    <h2><strong>微信的热补丁应用现状 </strong></h2>    <p>上一章节我们简单比较了各个热补丁的技术方案,它们解决了如何生成与加载补丁包的问题。但一个完善的热补丁系统不应该仅限于此,它还需要包括以下几个方面:</p>    <ul>     <li>网络通道;这里要解决的问题是决定补丁以何种方式推送给哪部分的用户。</li>     <li>上线与后台管理平台;这里主要包括热补丁的上线管理,历史管理以及上报分析,报警监控等;</li>    </ul>    <p> </p>    <p> </p>    <h3>一. 网络通道现状</h3>    <p>网络通道负责的将补丁包交付给用户,这个包括特定用户与全量用户两种情况。事实上,微信当前针对热补丁有以下三种通道更新:</p>    <ul>     <li>pull通道; 在登陆/24小时等时机,通过pull方式查询后台是否有对应的补丁包更新,这也是我们最常用的方式;</li>     <li>指定版本的push通道; 针对版本的通道,在紧急情况下,我们可以在一个小时内向所有用户下发补丁包更新。</li>     <li>指定特定用户的push通道;对特定用户或用户组做远程调试。</li>    </ul>    <p>事实上,对于大部分的应用来说,假设不实现push通道,CDN+pull通道实现起来还是较为容易。</p>    <p> </p>    <h3><strong>二. 上线与管理平台现状 </strong></h3>    <p>上线与管理平台主要为了快速上线,管理历史记录,以及监控补丁的运行情况等。</p>    <p><img src="https://simg.open-open.com/show/08ba73f734f18d77a892bed7ad495da2.png"></p>    <p> </p>    <p>事实上,微信发布热补丁是非常慎重的。它整个发布流程与升级版本是保持一致的,也必须修改版本号、经过严格的完整测试流程等。我们也会通过灰度的方式上线,同时监控补丁版本的各个指标。这里的为了完整的监控补丁的情况,我们做的工作有:</p>    <ul>     <li>1分钟粒度的每小时/每天的各版本累积用户,及时监控补丁版本的人数与活跃;</li>     <li>3分钟粒度的Crash统计,基准版本与补丁版本的Crash每小时/每天的两个维度对照;</li>     <li>10分钟粒度的补丁监控信息上报。</li>    </ul>    <p> </p>    <p> </p>    <h3>三. 补丁成功率现状</h3>    <p>应用成功率= 补丁版本人数/补丁发布前该版本人数 由于可能存在基准或补丁版本用户安装了其他版本,所以本统计结果应略为偏低,但它能现实的反应补丁的线上覆盖情况。</p>    <p>使用Qzone方案,微信补丁在10天后的应用成功率大约在98.5%左右。使用Tinker大约只有95.5%左右,主要原因在于空间不足以及后台进程被杀。在这里我们也在尝试使用重试的方式以及降低合成的耗时与内存,从而提升成功率。</p>    <p>热补丁技术发展的很快,Android推出的Instant App也令人期待。但是在国内,似乎我们还是指望自己更靠谱一点。每一个的应用的需求都不太一致,这里大致讲了一些微信的实践经验,希望对大家有帮助。</p>    <p> </p>    <h2><strong>未来工作 </strong></h2>    <p>随着微信部门内从“单APP”向“多APP”演进,微信也正在迈入开源化的开发实践。我们希望将各个功能组件化,从而做可以到快速复制与应用。微信的热补丁框架“Tinker”当前也在经历从微信分离,又合入到微信的过程。希望在不久的将来,我们也可以将“Tinker”以及微信中一些其他的组件开源出去。</p>    <p>我们也希望可以找一个App作为内测,给我们提供宝贵的意见。若对微信的Tinker方案感兴趣的用户,可以单独发消息或在文章末留言。注明姓名、所在公司以及负责的App,我们希望挑选部分产品作为内测。</p>    <p> </p>    <p>来自: <a href="http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1264" rel="nofollow">http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1264</a></p>    <p> </p>