热修复(HotFix)是一种在不重新发布应用的情况下,动态修复线上Bug的技术方案。
热修复的核心原理
1. 类加载机制
Android使用PathClassLoader和DexClassLoader加载类:
- PathClassLoader:加载已安装APK的dex文件
- DexClassLoader:加载任意路径的dex文件
2. 热修复的基本思路
shell原理:让类加载器优先加载修复后的类,覆盖有问题的类 实现方式: 1. 将修复代码打包成dex文件 2. 通过反射插入到dexElements数组前面 3. 类加载时优先找到修复类
主流热修复方案对比
| 方案 | 原理 | 优点 | 缺点 | 代表 |
|---|---|---|---|---|
| 底层替换 | 替换ArtMethod结构体 | 即时生效,无需重启 | 兼容性差,稳定性低 | AndFix、Sophix |
| 类加载 | 修改dexElements数组 | 稳定性高,兼容性好 | 需要重启生效 | Tinker、QZone |
| Instant Run | 自定义ClassLoader | 开发调试方便 | 仅适用于开发 | Google官方 |
详细方案分析
1. Tinker(微信)
shell原理: 1. 生成新旧APK的差分包(patch.dex) 2. 下载patch.dex到本地 3. 合并patch.dex和原APK的dex 4. 重启后通过修改dexElements加载新dex 特点: - 支持类、资源、so库替换 - 需要重启应用生效 - 差分包小,下载快
2. Sophix(阿里云)
shell原理: 1. 底层替换方案:替换ArtMethod的入口 2. 类加载方案:作为兜底方案 特点: - 即时生效,无需重启 - 支持方法级修复 - 收费方案,稳定性好
3. Robust(美团)
shell原理: 1. 编译期在每个方法插入逻辑 2. 运行时通过路由跳转到修复类 特点: - 即时生效 - 包体积增加少 - 需要提前插入代码
类加载方案实现细节
Dex插桩核心代码
javapublic class HotFix { public static void patch(Context context, File patchDexFile) { try { // 获取PathClassLoader ClassLoader classLoader = context.getClassLoader(); // 获取pathList字段 Object pathList = getField(classLoader, "pathList"); // 获取dexElements字段 Object[] dexElements = (Object[]) getField(pathList, "dexElements"); // 创建新的dexElements(包含patch.dex) Object[] newElements = makeDexElements(patchDexFile); // 合并数组:patch.dex在前 Object[] combined = (Object[]) Array.newInstance( dexElements.getClass().getComponentType(), newElements.length + dexElements.length ); System.arraycopy(newElements, 0, combined, 0, newElements.length); System.arraycopy(dexElements, 0, combined, newElements.length, dexElements.length); // 替换dexElements setField(pathList, "dexElements", combined); } catch (Exception e) { e.printStackTrace(); } } }
热修复的限制
1. 无法修复的情况
- AndroidManifest.xml的修改
- 新增四大组件
- 资源ID变化导致的资源引用错误
- 部分ROM的兼容性限制
2. 安全风险
- 代码注入风险
- 需要校验patch签名
- 传输过程需要加密
面试要点
- 理解类加载机制和双亲委托模型
- 掌握dexElements插桩原理
- 了解各方案的优缺点和适用场景
- 理解热修复的局限性和安全风险
- 熟悉Tinker、Sophix等主流方案