一、热修复的本质问题
热修复(Hot Fix / Hot Patch)的核心目标是:在用户不重新安装 APK 的情况下修复线上 Bug。这在移动应用发布中有巨大的商业价值——避免因为一个崩溃导致用户流失,也避免频繁发版带来的审核等待(特别是 iOS 端,Android 端虽然可以直接发版但用户更新率低)。
Android 热修复需要解决的根本问题是:如何让新代码在旧代码之前被执行? 在 Java/Android 中,”代码”的表现形式是 DEX 文件中的 Class,而 Class 的加载由 ClassLoader 完成。因此热修复的本质是操控 ClassLoader 的类加载顺序,让修复包中的类优先于原始 APK 中的类被加载。
二、Android 类加载机制
在深入热修复方案之前,必须理解 Android 的类加载体系。
2.1 DexPathList 与双亲委派
Android 的 ClassLoader 是 BaseDexClassLoader(API 26+)或 DexClassLoader/PathClassLoader。核心逻辑委托给了 DexPathList:
// AOSP: libcore/dalvik/src/main/java/dalvik/system/DexPathList.java |
关键的发现:DexPathList 遍历 dexElements 数组,返回第一个匹配的 Class。如果我们将修复的 DEX 文件插入到 dexElements 数组的最前面,那么 findClass 就会先找到修复后的类。
2.2 ClassLoader 的双亲委派模型
// AOSP: libcore/libart/src/main/java/java/lang/ClassLoader.java |
Android 中的 ClassLoader 层级:
BootClassLoader(加载 Framework 类,如 Activity、String) |
三、ClassLoader 方案:QQ 空间超级补丁
这是最早的开源方案之一。核心原理非常简单:反射修改 PathClassLoader 的 parent 字段,将修复 DEX 的 DexClassLoader 插入到类加载链中。
3.1 实现步骤
// 1. 获取 PathClassLoader 的 DexPathList |
3.2 QQ 空间方案的缺陷
CLASS_ISPREVERIFIED 问题:Dalvik 运行时(Android 4.4 以前),如果一个类在其 DEX 之外引用了其他类,且被引用者在同一个 DEX 中被打上
CLASS_ISPREVERIFIED标记,则运行时不会再次校验。但如果热修复修改了这个类,就会出现方法签名验证失败的问题。解决方案是在原始 APK 编译时插入一个”防校验”类,破坏CLASS_ISPREVERIFIED标记。ART 运行时已没有此问题。Android Nougat+ 混合编译:Android 7.0 引入了混合编译模式(JIT + AOT),
DexPathList的实现有变化,需要对不同的 API 级别做兼容处理。
四、Tinker 方案:全量 DEX 替换
腾讯开源的 Tinker(https://github.com/Tencent/tinker)是目前最成熟的热修复方案之一,已被微信团队大规模验证。
4.1 Tinker 的核心思路
Tinker 不插入单个修复 DEX,而是生成完整的修复后的 DEX 文件,替换掉原始 DEX。通过 BSDiff 算法生成差分包(patch),客户端下载差分包后与原始 DEX 合成新的 DEX。
4.2 BSDiff 算法与 BSPatch
BSDiff 是一个二进制差分算法,源自 Google 的 Chromium 项目。核心思想是:
新文件 = 旧文件 + 差分信息(新增/删除/修改的字节块) |
差分信息包括三部分:
- diff string:新旧文件中不同的连续字节。
- extra string:新文件中独有的连续字节。
- control tuples:三元组 (diff_pos, extra_pos, copy_len),控制合成过程。
BSPatch 合成伪代码:
// external/bsdiff/bspatch.c |
Tinker 的补丁生成流程:
原始 APK (old.apk) ──→ bsdiff ──→ patch.patch |
4.3 Tinker 的类加载机制
Tinker 使用自定义的 TinkerClassLoader,它替代了系统的 PathClassLoader。其 dexElements 中:
- 最前面:修复后的 DEX(由 bspatch 合成)
- 后面:原始 DEX
这样确保新类优先被加载。Tinker 同时支持 Application 类、Library、Resource 的热修复。
4.4 Tinker 的限制
- 必须重启应用:新的 DEX 文件需要重建 ClassLoader 才能生效。
- 不支持新增四大组件:Activity、Service、BroadcastReceiver、ContentProvider 的注册信息在 AndroidManifest.xml 中,无法通过 DEX 替换更改。
- OAT 兼容性:不同 ROM 对 dex2oat 的处理不同,某些机型上 oat 文件缓存可能导致类加载异常。
五、Sophix 方案:Native ART Method 替换
阿里开源的 Sophix(https://github.com/alibaba/Sophix)采用了更底层的方案——直接操作 ART 运行时的方法结构体。
5.1 ART Method 结构
在 ART 运行时中,每个 Java 方法在 Native 层对应一个 ArtMethod 结构体:
// AOSP: art/runtime/art_method.h |
5.2 Method Replacement 的核心原理
Sophix 的 native 层直接将新方法的 entry_point_from_quick_compiled_code_ 替换为旧方法的对应字段,或者在解释模式下替换方法的 DEX 指令指针:
// Sophix 核心逻辑(简化) |
5.3 Sophix vs Tinker vs QQ 空间
| 方案 | 原理 | 是否需重启 | Android 版本兼容 | 成熟度 |
|---|---|---|---|---|
| QQ 空间 | ClassLoader + dexElements 合并 | 是 | 4.0+ | Demo 级 |
| Tinker | BSDiff + 全量 DEX 替换 | 是(默认) | 4.0+ | 微信验证,非常成熟 |
| Sophix | Native ArtMethod 替换 | 可免重启(即时生效) | 4.4+ | 阿里商业验证 |
六、Android App Bundle 与 Google 的方案
Google 通过 Android App Bundle(AAB)和 Google Play 的 In-App Updates 提供了一套官方替代方案:
- Android App Bundle:Google Play 根据设备配置生成 Split APKs,用户只下载需要的代码和资源。
- In-App Updates:提供 Immediate 和 Flexible 两种模式,在应用内完成更新。
- Dynamic Delivery:通过 Play Feature Delivery 按需下载功能模块。
Google 并不鼓励热修复,因为热修复绕过了 Google Play 的安全审查机制,且可能引入不兼容问题。但这对于中国大陆市场(无法使用 Google Play)的应用来说,热修复仍然是最重要的基础设施之一。
七、面试常问题目
Q1: Android 类加载的双亲委派机制是什么?热修复如何利用它?
双亲委派机制:当 ClassLoader 加载一个类时,先委托给 parent ClassLoader 加载,如果 parent 没找到才自己加载。热修复通过反射将修复的 DEX 插入到 dexElements 数组最前面,因为 findClass 遍历 dexElements 时返回第一个匹配的类,修复后的类就会先于原始类被加载,从而”替换”了旧类。
Q2: Tinker 为什么需要重启才能生效?
Tinker 替换的是完整的 DEX 文件,而 DEX 文件在应用启动时被 PathClassLoader 加载到 dexElements 中。修改 dexElements 需要重新创建或修改 ClassLoader,这要求重启应用以重新初始化 Application 和 Activity。Sophix 之所以可以不重启,是因为它直接修改了 ART 运行时的 ArtMethod 结构体指针,绕过了 ClassLoader。
Q3: 热修复可以新增 Activity 吗?
ClassLoader 方案和 Tinker 方案都不能。Activity 必须在 AndroidManifest.xml 中声明,而 Manifest 在 APK 打包时就被固定了。不过可以通过”代理 Activity”模式间接实现:在 Manifest 中预注册一个代理 Activity,运行时由代理 Activity 通过反射创建目标 Activity 并转发所有生命周期方法。虚拟引擎方案(VirtualApp、RePlugin)则从根本上解决了这个问题。
Q4: CLASS_ISPREVERIFIED 问题是什么?
这是 Dalvik 虚拟机(Android 4.4 之前)特有的问题。Dalvik 在 DEX 优化时,如果一个类引用的所有外部类都在同一个 DEX 中,会给这个类打上 CLASS_ISPREVERIFIED 标记,表示”已验证”。当热修复修改了这个类的引用关系(比如调用了一个在不同 DEX 中的新方法),运行时校验会失败,抛出 IllegalAccessError。ART 运行时不再有此标记,因此 ART 以上不存在此问题。
参考源码路径:
- DexPathList:
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java - ClassLoader:
libcore/libart/src/main/java/java/lang/ClassLoader.java - BaseDexClassLoader:
libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java - ArtMethod:
art/runtime/art_method.h - BSDiff:
external/bsdiff/bspatch.c - Tinker:
https://github.com/Tencent/tinker - Sophix:
https://github.com/alibaba/Sophix







