上一篇讲了 ASM 字节码插桩,这篇是它的实战应用——热修复。线上 App 出了 bug,难道只能等下个版本?热修复让用户无感知就修好。理解它的原理能让你对 Android 类加载、dex 机制有全新认识。
什么是热修复
线上 App 出现紧急 bug(崩溃、白屏、严重逻辑错误)时,无需发版,用户下次启动就能加载到修复后的代码。这背后的技术:
类加载机制:修复的前提
Android 用 PathClassLoader 加载 APK 里的 dex,链路是:
PathClassLoader
└─ DexClassLoader (base/classes.dex)
└─ DexClassLoader (patch.dex, 优先级最高)
如果 patch.dex 里有一个和 classes.dex 同名同包的类,patch 里的版本会被优先加载。这就是 Tinker 的基础:用户的 bug 类只在 patch.dex 里,全量替换。
Tinker:全量替换派的代表
Tinker(微信)是开源热修复方案的事实标准。原理:
客户端运行 base APK(有 bug 的版本)
服务器下发 patch(差异计算出来的差分包)
客户端合并 patch 到 base.dex,生成新的 classes.dex
重启后,新 dex 取代旧 dex
Patch 的生成:bsdiff 算法对 base.dex 和 new.dex 做二进制差量,平均只有几 MB。
接入 Tinker:
// app/build.gradle
plugins {
id("com.tencent.tinker") version "1.9.14.4"
}
dependencies {
implementation("com.tencent.tinker:tinker-android:1.9.14.4")
}
// 自定义 Application
class MyApplication : TinkerApplication(
1, // tinkerFlags
"com.example.MyTinkerApplicationLike", // delegate class
"com.tencent.tinker.loader.TinkerLoader" // loader class
)
Tinker 的限制:
必须重启生效(不能边玩边修)
资源修改需要重新打包
dex 不能加新类,只能改已有类
Tinker 适合紧急 bugfix,不适合功能升级。
AndFix:方法级 hook
阿里 AndFix(Android Hot-Fix)走的是 Native 层路线:
方法调用栈:
├─ Java 方法 dispatch → ArtMethod 结构
└─ Native 层 hook → 替换 ArtMethod 的 entry_point
原理:ART 虚拟机中每个 Java 方法对应一个 ArtMethod 结构,里面有 entry_point_from_quick_compiled_code 字段。把这个字段改成新方法地址,旧方法被调用时实际执行新方法。不需要重启。
代码片段(AndFix 核心逻辑):
// andfix.cpp
void replaceMethod(JNIEnv *env, jclass clazz, jobject src, jobject dest) {
if (art_method_replace == nullptr) return;
// 拿到方法对象
jobject srcMethod = env->ToReflectedMethod(clazz, src, JNI_TRUE);
jobject destMethod = env->ToReflectedMethod(clazz, dest, JNI_TRUE);
// 替换 entry_point
art_method_replace(
env, srcMethod, destMethod
);
}
AndFix 的问题是:
不支持新增字段、新增方法(只能替换方法体)
复杂的泛型、lambda 处理时崩溃
不同 ROM 的 ART 实现差异大,兼容性差
阿里后来推出 Sophix 用 Java 层的 dex 替换替代了 Native hook,兼顾 Tinker 的稳定性和 AndFix 的即时性。
Robust:编译期插桩派
美团 Robust 的思路独树一帜:在编译期给每个方法插一层"开关":
// 原始方法
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initView();
}
// Robust 编译后
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initView();
}
public boolean dispatch() {
// 关键代码都在这里被引用
// 被 patch 替换的就是这个方法
return false;
}
执行时:
// 用户调用 onCreate → onCreate 引用 dispatch → dispatch 实际指向 patch
如果 patch 中替换了 dispatch 的实现,下次调用就走 patch 版本。
Robust 的好处是兼容性极好(不依赖 Native),坏处是必须用 Robust 编译才能生效,对原有构建流程侵入大。
各种方案的对比
实战:自研热修复需要的关键能力
Patch 生成:差分算法、签名校验、版本回滚机制
Patch 下发:CDN 加速、强制更新、降级策略
Patch 加载:dex 合成、类加载器隔离、ART/JVM 兼容
Patch 监控:成功率、Cold start 影响、内存占用
回滚机制:patch 加载失败自动回滚
业务上的真实场景
热修复不是"万能钥匙"。大部分公司用热修复处理的是:
启动崩溃、白屏黑屏
严重 UI 问题
安全漏洞(被劫持、隐私泄漏)
不要用热修复发新功能。运营层面的 A/B 测试、新功能灰度用 Play 的 Internal Testing 或专业 AB 测试平台。
一些反常识
Tinker 只支持 Android 4.0+,ART 后兼容改善
iOS 的 JSPatch/JSBox 不是热修复,是 JS 沙箱
Flutter 的 hot reload 是开发期工具,不是线上热修复
小程序的热更新是另一套体系(云端代码包)
小结
热修复原理覆盖了 Android 的大部分底层:dex 加载、ART 运行时、Native hook、编译期插桩。学习热修复就是站在应用层往底层钻,能极大提升 Android 内功。但真到自己选型,无脑选 Sophix 或者干脆不用(直接发版)是大多数团队的合理选择,毕竟热修复的工程成本不低。
下一篇聊聊 Flutter 与 Android 互操作,这是当下跨平台的真正主流方案。