热修复框架不替换已加载类,而是通过类加载器隔离(如将patch.dex插入dexElements头部)、native方法指针替换(ART下修改entry_point)、或代理插桩(新旧类共存+运行时跳转)实现逻辑更新,所有方案均回避Class卸载。

Java 中类加载机制本身不支持已加载类的字节码替换,热修复框架(如 Tinker、AndFix、Sophix)正是通过绕过 JVM 的类加载约束,在运行时“模拟”类更新效果。核心思路不是直接修改已加载类,而是让新字节码在后续实例化或方法调用中生效,具体实现依赖于类加载器隔离、方法替换、指令注入等技术,而非传统意义上的“替换 Class 对象”。
利用双亲委派破坏实现类的“覆盖加载”
标准双亲委派模型下,同一个类加载器不会重复定义同名类;热修复框架通常自定义 PathClassLoader 或 DexClassLoader 的子类,并在构造时插入修复 dex(如 patch.dex)到 dexElements 数组头部。这样当查找类时,优先命中补丁中的新版本类(例如 com.example.BugClass),而原 APK 中的同名类被自然跳过。这并非替换已有 Class,而是让新类实例化时走补丁路径。
- Android 8.0+ 需适配
DelegateLastClassLoader或反射修改pathList结构 - 补丁 dex 必须包含完整类(不能只放修改的方法),否则会因缺少父类/接口导致
NoClassDefFoundError - 仅对后续 new 实例或首次调用静态方法生效,已存在的对象不受影响
底层方法替换:绕过类加载直接改行为
对于已加载类的方法修复(如 AndFix 使用的 native hook),框架不依赖类重载,而是直接修改 ART 方法结构体(ArtMethod)中的入口函数指针(entry_point_from_quick_compiled_code_),将其指向修复后的新方法实现。这种方式无需重新加载类,对正在运行的对象也立即生效。
- 依赖 Android 版本和 ART 运行时内部结构,兼容性维护成本高
- 只能替换整个方法,不支持字段变更或新增方法
- 需 native 层权限,部分厂商 ROM 会限制此类内存写操作
代理与插桩:在不修改原类的前提下接管逻辑
像 Tinker 这类全量替换方案,实际采用“新旧类共存 + 运行时路由”策略:修复后的类以新名字(如 com.example.BugClass$Patch)打包进 patch.dex,再通过反射或字节码插桩,在原类关键方法中插入跳转逻辑(例如在 doWork() 开头加 if 判断,命中补丁则调用 BugClass$Patch.doWork())。原类仍存在,但行为被动态代理接管。
立即学习“Java免费学习笔记(深入)”;
- 需要构建期字节码增强(ASM/Javassist),修改原 APK 的 class 文件
- 补丁包体积较大,但兼容性好,支持字段、方法增删
- 首次调用有轻微性能开销(条件判断 + 反射调用)
ClassLoader 级别隔离与 Class 卸载的现实限制
JVM 规范不允许卸载已加载类,Android ART 也未开放 Class 卸载 API。因此所有热修复方案都回避卸载原类**,转而确保新类在新 ClassLoader 下加载并被业务代码使用。关键在于控制引用生命周期:避免静态变量持有旧类实例、及时清理线程局部变量(ThreadLocal)、防止 Activity/Fragment 持有旧类对象。否则即使新类已加载,老对象仍在内存中继续运行旧逻辑。
- 推荐使用弱引用缓存修复类,降低内存泄漏风险
- Application、全局单例等长生命周期对象需显式刷新内部状态
- 无法修复系统类(如 java.lang.String)或已启动的系统组件(如 Service)中的逻辑


















