0%

Android 三大热修复方案基本原理

什么是热修复?有什么用?

让应用能够在无需重新安装的情况实现更新,帮助应用快速建立动态修复能力。

热修复的使用场景在哪?有什么用?

轻量而快速的升级

热修复是一个动态修改代码与资源的方式,适合于修改量较少的情况。

传统的APP发布升级流程慢,热补丁可以在更短的时间内发布,非常适合在灰度阶段快速验证问题是否已修复,大大缩短发布流程。

热补丁技术可以降低开发成本,缩短开发周期,实现轻量而快速的升级。

远端调试

排查用户反馈的问题会遇到”本地不复现”,”日志查不出”,”联系用户不鸟你”等情况,只要能向特定用户发送补丁,就可以很方便的排查问题,不用依赖用户操作,可以默默的就排查解决。

数据统计

可以方便的进行ABTest,对不同的用户群发送不同的补丁,做不同的行为统计

热修复有什么局限性?是否能代替发布?

热修复无法完全代替APP升级发布。

  1. 补丁只能针对单一客户端版本,随着版本差异变大补丁体积也会增大;
  2. 补丁不能支持所有的修改,例如AndroidManifest;
  3. 补丁无论对代码还是资源的更新成功率都无法达到100%。

主流热修复方案原理是什么?

目前主流热修复方案可分为:

  1. Native层替换方法结构体
  2. Java层干预ClassLoader加载Dex
  3. instant-run 插桩方案

Native层方案

Native派的做法大致有以下两种:

代表框架:Epic、Dexposed、Andfix

优点

  1. 实时生效,不需要重新启动App
  2. 不仅可以hook自己的代码,还可以hook同进程的Android SDK代码

缺点

  1. 无法支持新增或者删除类的filed的情况,因为没有整体替换class, 而field在class中的相对地址在class加载时已确定
  2. 兼容性问题严重,由于 Android 系统每个版本的实现都有差别,不同的手机厂商定制的系统千差万别,所以需要做很多的兼容

适用场景

native hook不适用于:

  1. 引起原有类中发生结构性变化的修改
  2. 修复了的非静态方法会被反射调用

新增一个完整的、原包中不存在的新类是可以的。

原因是一旦补丁中出现了方法的增加或减少,会导致这个类以及整个Dex的方法数变化,随之方法索引就会变化,访问方法时无法索引到正确的方法了。

Java层干预类加载方案

代表框架:微信Tinker

优点

  1. 不需要适配虚拟机的变化,没有很大的兼容性问题。
  2. 代码非侵入,对apk体积没有影响。
  3. 虚拟机在安装期间为类打上CLASS_ISPREVERIFIED标志是为了提高性能的,我们强制防止类被打上标志会影响性能。

缺点

  • 需要反射更改DexElements,改变Dex的加载顺序,这使得patch需要在下次启动时才能生效,实时性就受到了影响。
  • 这种方案在android N [speed-profile]编译模式下可能会有问题,可以参考Android N混合编译与对热补丁影响解析

instant-run 插桩方案

代表框架:美团Robust

原理:

  1. Robust插件对每个产品代码的每个函数都在编译打包阶段自动的插入了一段代码,插入过程对业务开发是完全透明
  2. 编译打包阶段自动为每个class都增加了一个类型为ChangeQuickRedirect的静态成员,而在每个方法前都插入了使用changeQuickRedirect相关的逻辑,当 changeQuickRedirect不为null时,可能会执行到accessDispatch从而替换掉之前老的逻辑,达到fix的目的。

优点

  • 几乎不会影响性能(方法调用,冷启动)
  • 支持Android全版本
  • 高兼容性(Robust只是在正常的使用DexClassLoader)、高稳定性,修复成功率高达99.9%
  • 补丁实时生效,不需要重新启动
  • 支持方法级别的修复,包括静态方法
  • 支持增加方法和类
  • 支持ProGuard的混淆、内联、优化等操作

缺点

  • 代码是侵入式的,会在原有的类中加入相关代码
  • so和资源的替换暂时不支持
  • 会增大apk的体积,平均一个函数会比原来增加17.47个字节,10万个函数会增加1.67M。
  • 会增加少量方法数,使用了Robust插件后,原来能被ProGuard内联的函数不能被内联了