Android热修复技术

热修复提出于2014年,兴起于2016年,尤其是在Instant run 问世以后,各种热修复技术相继涌出,一种摆脱传统发版方案直接使用补丁来更新app内容无疑是近几年较火的新技术(虽然苹果2017年禁止热更新)。

在热修复出现之前,一个已经上线的APP中如果出现了bug,即使是一个非常小的bug,不及时更新的话有可能存在风险,若要及时更新就得将APP重新打包发布到应用市场后,让用户再一次下载,这样就大大降低了用户体验,当热修复出现之后,这样的问题就不再是问题了。

热更新的提出源于产品需要快速迭带而又不能经常提示用户更新影响用户体验而出来的一种在不用更新应用的情况下修复bug的解决方案。目前较火的热修复方案大致分为两派,分别是:

阿里系:Andfix(旧)、Hotfix、Sophix(Hotfix最新版):https://help.aliyun.com/product/51340.html?spm=a2c4g.11186623.6.52.x0WM2m。
腾讯系:QQ超级补丁(旧)、Tinker:http://tinkerpatch.com/

当然还有其他团队的产品,如月了么的Amigo,美团的Robust等等。
建议:做产品还是站在巨人的肩膀上,除非自已有实力解决所有问题。

一、热更新的技术原理

从本质上讲,热更新热修复什么的,就做一件事情,替换。当替换的东西属于大块内容的时候,就是模块化了,当你去替换方法的时候,叫热更新,当你替换类的时候,叫热插件。就这么简单的理解。无论是替换一个类,还是一个方法,都是在干替换这件事请。

1.1 Android热修复动态替换

先用一个形象的语言来描述Android热修复中动态替换的过程,通常遇到的Bug有:

  • 边际问题,临界条件没有经过充分验证,导致碰到各种临界条件时出现的异常
  • 逻辑问题,某块功能逻辑实现有问题
  • 功能未实现,某功能功能未实现或只实现其中的部分功能
  • 兼容性问题,特别是与外设有交互的应用
  • Crash问题,程序代码不够健壮导致App运行时崩溃

在一个理想的状态下,我们只需将修复好的这些class更新到用户手机上的app中就可以修复这些bug了。所以不管是哪种热修复方案,肯定是如下几个步骤:

  • 推送补丁(内含修复好的class)到用户手机,即让APP从服务器上下载(网络传输)
  • APP通过Android的类加载器,使补丁中的class被APP调用(本地更新)

通过类加载器加载这些修复好的class,覆盖对应有问题的class,理论上就能修复bug了

1.2 Android端的类加载原理

Android跟java有很大的渊源,基于jvm的java应用是通过ClassLoader来加载应用中的class的,Android对jvm优化过,使用的是dalvik,且class文件会被打包进一个dex文件中,底层虚拟机有所不同,那么它们的类加载器当然也是会有所区别,在Android中,要加载dex文件中的class文件就需要用到 PathClassLoader 或 DexClassLoader 这两个Android专用的类加载器。

  • PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器
  • DexClassLoader:用来加载dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现热修复的重点

如需深入研究,可参考《热修复——深入浅出原理与实现》,文中作者深入分析了热修复原理,并基于以上原理实现了一个基础的热修复框架,实现过程分析的非常细致深入,非常适合做为热修复入门原理的了解。

二、方案比较

2.1 Qzone(QQ超级补丁-Nuwa)

Qzone的超级热修复方案是业界最早的热修复方案之一,原理简单而巧妙。(但已经2年没有更新了,应该是团队放弃了该项目)
热修复方案:基于Android的dex分包基础上,把需要修复的类打包成一个dex文件下发,并在APP启动时通过反射,将这个dex文件放在dexElements的最前面,这样修复了的Class就会比有Bug的Class优先加载了。
但是这种方案有一个缺点,由于新增了dex,如果修复的类到了一定数量就会影响启动性能,特别是在ART模式下更容易导致补丁包异常大。

2.2 Tinker

Tinker现在很稳定了,也开始收费,话说回来,如果Qzone没有上面缺陷,或许就不会有Tinker了。对于微信这样一个对性能有极高要求的产品来说,Qzone的缺点会被无限放大。在参考Instant Run的冷插拔与buck的exopackage后,Tinker采用了全量替换的策略。全量替换可以避免插桩和地址写死问题,但是补丁包会很大,因此可以在新旧两个Dex的差异放在补丁包中,下发到移动端后再在本地合成完整的dex文件。

热修复流程:应用启动的时候,会去检查有没有fix_classes.dex这个文件,若是没有这个文件,就会去后台查找有没更新补丁,若没有,就按原流程运行,若是有新的补丁包,则启动下载,下载完成开启新的进程服务TinkerPatchService,进行dex合并,合并成功之后等待应用重启,补丁修复生效。

实际上,Tinker保留了Qzone最核心的东西:反射修改dexElements。无论是插入还是替换,本质都是利用了类加载的特点。由于需要下发的全量补丁包体积过大,Tinker采用了后台求diff,下发diff文件,移动端合成全量包的策略。

如果仅此而已,只要有diff/patch算法,就可以开发Tinker了。实际上,确实如此。而Tinker第二个创新之处就是采用了自研的DexDiff算法,大大优化了下发差异包的大小。

[Github:] https://github.com/Tencent/tinker

2.3 Sophix方案(Hotfix最新版)

阿里的热修复技术演进:Dexposed->AndFix->HotFix 1.x -> HotFix 2.0(Sophix),
从最初的只支持方法修复到现在可以实现全修复,并且支持所有Android系统版本,稳定性和兼容性、安全性都得到了大的提升。
Dexposed:开源项目,但也是两年没有更新,应该也是放弃了;AOP框架,可用于日志记录,性能统计,安全处理等,支持函数级别的在线热修复技术;但系统只支持到Dalvik2.3,4.4,CPU仅支持ARM, 仅支持方法级别上的修复,不支持函数的增减。
AndFix:开源项目,与Dexposed差不多,只是阿里内部其他团队开发)的,但对Android版本的兼容比Dexposed要好,可以支持到Android7.0,但同样不支持函数的增减,是2016年10月公开的热修复支持。
Sophix:是阿里百川最新推出的一款无侵入的热修复方案,但阿里没有对Sophix开源;支持函数增减,也支持资源的更新,支持Android7.0以上版本,也兼容大部份机型,集成是一种傻瓜式的集成。

这里重点介绍Sophix的到底是怎样实现的:
Sophix方案中提出了一个思想就是不需要去关心方法的结构体内部具体是什么样的,只需要进行一次整体的替换就可以了。原来需要一个成员一个成员地进行遍历替换,现在是整体替换,但是本质是没有区别的。同时也不关心结构体内部要做哪些操作以及每个成员变量所代表的含义,只需要进行整体替换就可以了。这样做有两个好处,一个是比较简单,另一个就是不需要了解虚拟机每个方法体的内部结构,所以理论上对于所有的虚拟机版本都是适用的。

Sophix在热修复过程中是不侵入打包过程的,而是通过补丁工具生成补丁。由于热部署Andfix修复正在运行的方法有Crash风险,所以补丁工具提供参数由业务方来决定是否尝试走热部署;如果用户Patch的方法没有被高频调用同时又有实时生效的需求,那么可以优先选择走热部署方案;但这并非绝对,当代码变更导致热部署不支持时,还是会转向冷部署。
(热部署与冷部署的区别就是否重启应用才能看到效果)

几种热修复对比如下:

平台 Sophix Andfix Tinker QQ超级补丁
即时生效 Yes Yes No No
性能损耗 较小 较小 较大
侵入式打包 无侵入式打包 依赖侵入式打包 依赖侵入式打包
Rom体积 较小 较小 较小 较大
接入复杂度 傻瓜式接入 比较简单 相对复杂 比较简单
补丁包大小 较小 较小 较小 较大
全平台支持 Yes Yes Yes Yes
类替换 Yes Yes Yes Yes
so替换 Yes No Yes No
资源替换 Yes No Yes Yes

三、总结

随着各大平台的投入,现在Android热修复技术已经很稳定,对接文档也很详细,也都提供很友好的人机界面,项目接入是不存在问题。面对实际的开发,选择使用或者说选择哪种方案,必须符合实际的应用的场景,一句话,没有最好的,只有合适的。虽然技术可以多学习,热修复却并不一定适合商业项目,阿里和腾讯两大巨头的平台有收费机制(若要接入的话建议使用大平台)及看自已的项目没有必要做热修复。