0%

05-热修复-第一节

热修复这块知识,对于常用的解决方案(开源框架)需要了解,尤其是 类替换即时生效 这 2 个维度。

常见热修复框架对比

二、Robust 热修复原理

美团开源的,抖音也是用这个。它是 Java 开发的,但是它是怎么实现的即时生效?不是说只能在 NDK 去替换有问题的方法吗?其原理是:Robust的 gradle 插件对每个产品代码的每个函数在编译打包阶段自动插入了一段代码,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//原始代码
public long getIndex() {
return 100;
}


//处理之后的代码
public long getIndex() {
if(changeQuickRedirect != null) {
//PatchProxy中封装了获取当前className和methodName的逻辑,并在其内部最终调用了changeQuickRedirect的对应函数
if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) {
return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue();
}
}
return 100L;
}

而修复后的patch包里面是这样的:

1
2
3
4
5
6
7
8
9
10
public class StatePatch implements ChangeQuickRedirect {
@Override
public Object accessDispatch(String methodSignature, Object[] paramArrayOfObject) {
String[] signature = methodSignature.split(":");
if (TextUtils.equals(signature[1], "a")) {//long getIndex() -> a
return 106;
}
return null;
}
}

所以呢,Robust 的实现也就是,打一个 补丁包,在本地利用类加载技术,将补丁加载进来。补丁是实现了 changeQuickRedirect 接口的,这样,当实例化补丁之后,可以通过反射的方式将补丁的实例赋值给类的 changeQuickRedirect 对象,所以,当有补丁包的时候 (changeQuickRedirect 就不会为 null 了,因此就不再执行原有逻辑,而是执行 changeQuickRedirect 的逻辑了 。

按照RoBust 官方的说法,这个方案规避了需要针对虚拟机、指令集的 Native 方案,在 Java 层就实现了热修复。它其实参考的还是 Instant Run

详细可以参考Android热更新方案Robust - 美团技术团队 (meituan.com)

Tinker

光从代码上讲(不考虑资源)Tinker 是打出差量包 patch ,然后在 App 端将 old.dex + patch包 = 修复bug的 dex。

在 ActivityThread 中(handleApplication 这个方法中),会创建出用于加载我们所写的项目代码的 PathClassLoader ,关于 classLoader ,有以下几个需要注意:

  • AppcompatActivity 是用 PathClassLoader 加载的,因为我们需要在gradle 里面引入 appcompat ,所以它和okhttp之类的引用没什么区别,都是属于项目代码

  • Activity 是用 BootClassLoader, 这个没问题,因为它是 sdk 的

  • Application.class.getClassLoader 肯定也是 BootClassLoader

  • 在 我们自己写的 MyApplication 中我们直接 getClassLoader() 获得的是我们整个程序所属的 ClassLoader,所以也是 PathClassLoader

一定要理解 Android 的类加载机制,然后才能知道 dexElements[] 那些dex 数组是怎么存放的,图片如下:

Android类加载机制

同学提问

有同学提问,补丁包下发之后,之前的 dex 能不能删掉?因为 补丁的dex 已经在 dexElements[] 数组最前面了。答案是肯定不能删除,我老的 dex 中可能有 A 类和 B 类, 但是补丁包只是为了修复 有bug 的A 类,所以补丁中没有 B 类的,如果把老的 Dex 删除了,那么后续要用到 B 类就没有了。

制作补丁包

如何制作补丁包呢?其实还是利用Android自己的打包工具,dx 命令,具体流程如下:

  1. 修复Bug ,然后编译,将有问题的类(比如是 com.test.Utils.java)生成 .class 文件

  2. 执行命令打包成 dex 或者 jar :

    dex –dex –output=patch.dex com/test/Utils.class

    注意,如果是想打包成 jar ,就将 patch.dex 改成 patch.jar 即可。

如何使用补丁包?

下载下来补丁包之后,其使用方法与诸如插件化之类的就很像了,都是涉及类加载,反射等,具体步骤有以下 6 步:

  1. 获取程序(当前应用)的 PathClassLoader 对象

  2. 反射获得 PathClassLoader 的 parent 的 BaseDexClassLoader 的 DexPathList 类型的 pathList 对象

  3. 反射获取 pathList 中Element[] 类型的 dexElements 对象(这是 oldElements 集合)

  4. 把补丁包变成 Element 数组 patchElements(反射执行 DexPathList 类中的 makePathElements 方法)

  5. 合并两个 Element 数组 oldElements + patchElements = newElements (Array.newInstance() 方式即可合并2个数组)

  6. 反射把 pathList 中的 dexElements 赋值成 newElements

注意,热修复补丁包一定要放在 dexElements 数组的最前面,需要先加载才能达到修复的目的!

so的修复也差不多,他们也是放在一个 Element 数组里面。

谢谢你的鼓励