2.7 热修复方案

1 minute read

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);
      ...
    

2.7.6 so修复

参考