热修复方案对比

热修复方案对比

热修复简介

正常开发流程

正常开发流程需要用户主动去下载更新app,修复时间较长。apk文件大。

热修复开发流程

热修复开发流程

只要打开app就能自动更新。补丁包小。快速修复bug。

冷启动代码修复方案

腾讯系

腾讯系热修复方案

Tinker
  • 原理

一个ClassLoadere可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当查找类的时候,按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找到类则返回,如果找不到从下一个dex文件继续查找。因此,通过把有问题的类修复之后打包到一个dex中,然后把这个dex插入到Elements的最前面就可以实现类的替换,从而解决这个类的bug。

Tinker提供dex差量包patch.dex的形式,然后将patch.dex与原应用的classes.dex合并成一个完整的dex,完整dex加载得到的dexFile对象作为参数构建一个Element对象然后整体替换掉旧的dexElements数组。

  • 优点

极致的dex补丁差量操作。生成的补丁包时比较粒度十分精细,实现了代码级别的差异比较,而不是传统的BsDiff算法。合成的时候也是根据补丁和原有的apk的dex进行精细的合成操作。

  • 缺陷

1.由于其精细合成的问题在于性能损耗,dex合成是在一个单独的进程中进行的。dex merge操作是在java heap上进行的,如果此时进程申请的java heap对象超过了vm heap规定的大小,进程OOM,memory killer可能会杀掉该进程,导致dex合成失败。

打补丁包的成功取决于合成操作的成功率。

  • 已知问题

    1.在AndroidN上,补丁对应用启动时间有轻微的影响。

    2.不支持一些操作系统是android-21的部分三星手机,加载补丁时会主动抛出”TinkerRuntimeException:checkDexInstall failed”

    3.对于资源替换,不支持remoteView.例如transition动画,notification icon以及桌面图标。

Amigo

  • 核心实现

主要介绍一下Amigo和其他方案的不同点,以及其实现原理。

  • 修复class

上边说到腾讯系的方案是反射操作DexPathList中的dexElements。Amigo的补丁包是一个完整全新的apk,解压dex后,先对所有的dex文件进行dexopt/dex2oat操作,然后重新构建一个ClassLoader。其目的是为了1:加快启动的速度。2.不需要大量的反射处理提升了兼容性。

  • 增加四大组件

Amigo同其他方案的区别在于能够增加四大组件。其实现方式其实就是类似插件化的原理,因为其补丁包是apk的形式,Activity通过hook加载流程,打开对应launch mode的StubActivity,再反射注入新Activity的实例;Service、Provider也是通过ServiceStub、ProviderStub实现;BroadcastReceiver是变静态注册为动态注册实现的。

以Activity的启动时序为例,标注出其中的hook点:

  • activity hook差分实现

一般情况下,是通过下发完整的apk,但是如果全量下发会存在下发成功率的问题。因此在Amigo可以自己实现差分算法,Amigo也提供了基于APK内部细粒度的ZipPatch,也可以自己选择GooglePlay上的archive-patcher,也可以是自己实现的差分算法。

局限
  • 不支持和Instant Run同时使用

  • Amigo的实现是通过单一ClassLoader、单一AssetManager的app,在一些多classloader的插件化框架中可能不兼容

  • RemoteViews的自定义布局不支持修改,只支持内容修复

  • Amigo相对于其他方案来说,其能够实现四大组件的增加。

  • 其最大的缺陷在于补丁包是一个完成的apk,对于大一点的app很大程度上限制的下发成功率。以及其差分粒度是不如DexDiff。

    使用风险
    • 后台服务Amigo平台已不再支持Amigo服务

    • 随着amigo的核心人员去了阿里和其他公司,两三年前已停止维护

热替换代码修复 && 冷启动代码修复

阿里系

阿里系热修复方案

Sophix方案是同时支持即时生效和冷启动修复

热替换代码修复原理

在旧的热替换方案Andfix方案中,是通过直接在已经加载的类中的native层替换掉原有方法,是在原有类的基础上进行修改的。每一个Java方法在Art虚拟机中对应着一个ArtMethod,ArtMethod中记录了这个Java方法的所有信息,包括所属类、访问权限,代码执行地址等。因此通过构造一个新的ArtMethod,将其所有字段替换掉原有ArtMethod的所有字段,可以实现代码的替换。

但是由于各手机厂商是能够对ArtMethod结构体进行修改,如果对其进行了修改,就和原有开源代码里的结构不一致,那么这种替换机制就会出问题。因此,这种方案的最大的问题就在于依赖于ArtMethod等ROM底层方法结构而导致的兼容性问题,在一些修改了底层的虚拟机结构的机器就无法支持。

在Sophix中将原有所有字段逐一替换的方式改成了通过在底层memcpy的方法来完整替换ArtMethod。只要像这样把ArtMethod整个结构体完整替换,就能够把所有旧方法成员自动对应的换成新方法的成员。

冷启动修复原理

上边说到腾讯系的热修复原理是通过把原有的dex和补丁包里的dex重新合成一个。Sophix的冷启动修复思想是把原先基线包里的dex中,去掉补丁中也有的类。这样,补丁+去除了补丁类的基线包就等于新App中的所有类。不过它不是真正的去掉基线包中的类,而是移除定义类的入口,对于类的具体内容不进行删除。

热替换修复的优缺点

优点:热替换方式的修复无需重启可以直接生效,这种方式修复实时性更高。

缺点:由于是在应用运行期间发生了变动,如果修改了某个方法的逻辑,就会导致它在修复前后的逻辑不一致,引发一些诡异的错误。因此热替换方式的热修复只适用于修复一些简单的BUG,如果要做一些功能方面的更新,不建议采用。

方案对比

Tinker Sophix Amigo
类替换 yes yes yes
lib替换 yes yes yes
资源替换 yes yes yes
全平台支持 yes yes 2.1-7.1
即时生效 no yes no
性能损耗 中,有合成操作 低,冷启动下有些损耗
补丁包大小

热补丁的更新方式

1.pull通道,在登录的时候通过pull查询后台是否有对应的补丁包更新,这也是最常用的方式。这还需要后台搭建,提供补丁后台托管,版本管理,保证传输安全,管理历史记录,监控补丁的运行情况等。

2.指定版本的push通道

Tinker的有TinkerPatch,不过它也是收费的,可以针对我们的用户量来考虑是否使用。支持补丁下发,版本管理,补丁回退等功能。如果采用Tinker方案可以先考虑接入TinkerPatch。

已知问题

1.主流的热修复框架Tinker,Sophix在最新的版本也支持了新增Activity,不过方式跟组件化一样,需要提前在原有AndroidManifest.xml文件中插桩实现。

2.由于GooglePlay开发者条款协议,不建议在GooglePlay平台动态更新代码

总结

对比主流的Tinker方案和Sophix方案,性能相差并不太多。但由于Sophix是按照用户量收费的,其接入成本,维护自然会容易些,而且有配套的服务后端。不过考虑虽然可能我们的app现在的用户量会少一点,不过考虑到后期可能的流量增加,其收费也是较高的。而且其具有的热替换代码修复也是有局限性,优势并不大。而对于其他框架Amigo来说,它的优势在于能够添加四大组件,不过考虑到热修复应该只能作为紧急修复线上严重问题时的补救措施,其核心在于下发的成功率和稳定性,对于添加组件的功能倒是其次。而且随着Amigo两三年前停止维护,其稳定性和新版本的兼容也是不够的。综上,可以考虑Tinker的方案来进行热修复的接入。

最后,热修复作为一个临时解决线上问题的一个解决方案,我们应该尽量避免使用它们,应该尽量提高正常迭代版本的稳定性,以及合理的迭代周期,而不是太过于依赖热修复这种线上解决问题的这种方式。

参考 && 扩展阅读

《深入探索Android热修复技术原理》

Tinker

Amigo

Tinker Dexdiff算法解析