2.7 热修复方案
2.7.1 类加载方案
- 原理:基于类加载器,
DexClassLoader
, 修改dexElements
元素的位置实现热修复; - 流程: 1. 获取当前应用的ClassLoader, 即BaseDexClassLoader, 2. 通过反射获取DexPathList属性对象PathList, 3. 通过PathList方法把patch.dex转成Element[], 4. 合并Element[]并将patch.dex放到队头, 5. 加载Element[].
2.7.2 Native替换
- 原理:运行时修改Native的Field指针的方式, 实现方法的替换.
- 流程:1. 打开链接库得到操作句柄, 获取native层内部函数, 得到ClassObject对象, 2. 修改访问权限为public, 3. 得到新旧方法的指针, 新方法指向目标方法, 实现方法的替换
2.7.3 Instant Run
- 原理:使用ASM, 注入方法…ASM是Java字节操作框架, 动态生成类或增强既有类的功能. ASM可以直接产生二进制class文件, 也可以类被加载前动态改变行为
- 热部署
- 温部署
- 冷部署
2.8 动态代理
- 实现原理:核心类是Proxy和InvokeHandler, 由JVM负责生成$proxy0实现类, 该类通过反射调用自定义实现代理类的方法
2.7.4 代码修复
- 类加载方案
- 优点: 修复范围广, 限制少; 缺点: 时效性差, 需要冷启动后生效
- 底层替换
- 优点: 时效性高, 加载轻快; 缺点: 限制多
- 实现原理:
// xxx/AndFix.java // Method为Java层反射机制获取 private static native void replaceMethod(Method src, Method dest); // xx/andfix.cpp // replaceMethod的Native层实现 static void replaceMethod(JNIEnv* env, jclass clazz, jobject src, jobject dest) { if (isArt) { art_replaceMethod(env, src, dest); } else { dalvik_replaceMethod(env, src, dest); } } // art_method_replace.cpp // art的replaceMethod实现.. extern void __attribute__ ((visibility ("hidden"))) art_replaceMethod (JNIEnv* env, jobject src, jobject dest) { // 由于底层的ARTMethod数据结构均不一致, 需要根据版本兼容... if (apilevel > 23) { replace_7_0(env, src, dest); } else if (apilevel > 22) { replace_6_0(env, src, dest); } ... } // art_method_replace_6_0.cpp void replace_6_0(JNIEnv* env, jobject src, jobject dest) { // 通过Java的Method对象获取对应的Arthod的地址 art::mirror::ArtMethod* smeth = (art::mirror::ArtMethod*) env->FromReflectedMethod(src); art::mirror::ArtMethod* dmeth = (art::mirror::ArtMethod*) env->FromReflectedMethod(dest); ... // 方法执行入口, 如果art是解析模式, 取出Dex Code, 取的该指针 smeth->ptr_sized_fields_.entry_point_from_interpreter_ = dmeth->ptr_sized_fields_.entry_point_from_interpreter_; // 方法执行入口, 如果art是AOT, 取出Dex Code编译后的机器码, 取的该指针 smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code = dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code; }
- 虚拟机方法调用原理
// art/runtime/art_method.h class ArtMethod FINAL { ... protected: ... struct PACKED(4) PtrSizedFields { // art解析模式的方法入口 void* entry_point_from_interpreter_; void* entry_point_from_jni_; // art AOT模式的方法入口 void* entry_point_from_quick_compiled_code; } ptr_sized_fields_; }
- 兼容性问题
ArtMethod
被厂商修改, 导致replace_method失效
// 1. 将replace_x_x(如replace_6_0)替换成memcpy memcpy(smeth, dmeth, sizeof(ArtMethod)); // sizeof(ArtMethod)即两个方法size_t的差值 memcpy(smeth, dmeth, methSize)
2.7.5 资源修复
-
Instant Run资源修复
// Instant Run资源修复核心逻辑 public static void monkeyPatchExistingResource(Context context, String externalResourceFile, Collection<Activity> activities) { ... // 1. 创新新的AssetManager, 并反射调用mAddAssetPath, 增加新的资源包 AssetManager newAssetManager = AssetManager.class.getConstructor().newInstance(); Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); mAddAssetPath.setAccessible(true); if ((Integer)mAddAssetPath.invoke(newAssetManager, externalResourceFile)==0) { ... } // 2. 反射Activity的AssetManager, 替换成新的AssetManager // 3. 反射Resource, 把他们的AssetManager成员替换成新的AssetManager // 代码细节忽略, 可以直接看深入探索Android热修复介绍或看Instant Run的源码 ... }
-
Sophix资源修复
... Method initMeth = assetManagerMethod("init"); Method destroyMeth = assetManagerMethod("destroy"); Method addAssetPathMeth = assetManagerMethod("addAssetPath", String.class); // 析构AssetManager, native层的AssetManager析构函数会释放加载了的资源 destroyMeth.invoke(am); // 重新构造AssetManager, 调用init重新初始化, 即可AssetManager::getResTable()会重新解析 initMeth.invoke(am); // 置空mStringBlocks assetManagerField("mStringBlocks").set(am, null); // 重新加载资源路径 for (String path : loadedPaths) { addAssetPathMeth.invoke(am, path); } // 增加patch资源路径 addAssetPathMeth.invoke(am, patchPath); assetManagerMethod("ensureStringBlocks").invoke(am); ...