Android热修复原理与实战
热修复
什么是热修复
在我们应用上线后出现bug需要及时修复时,不用再发新的安装包,只需要发布补丁包,在客户无感知下修复掉bug
怎么进行热修复
服务端:补丁包管理
用户端:执行热修复
开发端:生成补丁包
热修复要解决的问题
客户端
- 补丁包是什么?
- 如何生成补丁包?
- 开启混淆后呢?
- 对比改动自动生成补丁包(gradle)?
服务端
- 什么时候执行热修复?
- 怎么执行热修复(使用补丁包)?
- Android版本兼容问题?
热修复解决方案
热补丁方案有很多,其中比较出名的有腾讯Tinker、阿里的AndFix、美团的Robust以及QZone的超级补丁方案。
AndFix
在native动态替换java层的方法,通过native层hook java层的代码
Robust
Android热更新方案Robust - 美团技术团队 (meituan.com)
public long getIndex() {
// 有BUG的代码片段
return 100;
}
public static ChangeQuickRedirect changeQuickRedirect;
public long getIndex() {
// 经过插桩后实际执行的代码
if(changeQuickRedirect != null) {
return 修复的实现;
}
return 100L;
}
Tinker
Tinker通过计算对比指定的Base Apk中的dex与修改后的Apk中的dex的区别,补丁包中的内容即为两者差分的描述,运行时将Base Apk中的dex与补丁包进行合成,重启后加载全新的合成后的dex文件
ClassLoader
双亲委派机制
含义
某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。
作用
1、避免重复加载,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2、安全性考虑,防止核心API库被随意篡改。
类查找流程
类加载实现热修复
流程
1、获取程序的PathClassLoader对象
2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象
3、反射获取pathList的dexElements对象 (oldElement)
4、把补丁包变成Element数组:patchElement(反射执行makePathElements)
5、合并patchElement+oldElement = newElement (Array.newInstance)
6、反射把oldElement赋值成newElement
代码实现
HotFix.installPatch(this, new File("/sdcard/patch.jar"));
HotFix.java类
package com.example.simpledemo;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class HotFix {
public static void installDex(Context context, String dexPath) {
try {
// 获取pathClassLoader
PathClassLoader classLoader = (PathClassLoader) context.getApplicationContext().getClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, null, null, classLoader);
// 获取pathList对象
Object newPathList = FieldUtils.getField(dexClassLoader.getClass(), "pathList", true).get(dexClassLoader);
Object oldPathList = FieldUtils.getField(classLoader.getClass(), "pathList", true).get(classLoader);
if (newPathList == null || oldPathList == null) return;
// 获取dexElements数组
Object newDexElements = FieldUtils.getDeclaredField(newPathList.getClass(), "dexElements", true).get(newPathList);
Object oldDexElements = FieldUtils.getDeclaredField(oldPathList.getClass(), "dexElements", true).get(oldPathList);
if (newDexElements == null || oldDexElements == null) return;
// 合并dexElements数组
Class<?> componentType = newDexElements.getClass().getComponentType();
if (componentType == null) return;
int newArrayLen = Array.getLength(newDexElements);
int oldArrayLen = Array.getLength(oldDexElements);
Object array = Array.newInstance(componentType, newArrayLen + oldArrayLen);
if (array == null) return;
System.arraycopy(newDexElements, 0, array, 0, newArrayLen);
System.arraycopy(oldDexElements, 0, array, newArrayLen, oldArrayLen);
// 将合并后的dexElements重新赋值给原dexElements
// Note:一定要重新获取一下pathList对象,否则会报错
Object pathList = FieldUtils.getField(classLoader.getClass(), "pathList", true).get(classLoader);
if (pathList == null) return;
Field field = FieldUtils.getDeclaredField(pathList.getClass(), "dexElements", true);
FieldUtils.writeField(field, pathList, array, true);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
代码中用到的FieldUtils类是一个开源库中的类,依赖即可
implementation 'org.apache.commons:commons-lang3:3.12.0'
热修复原理
依据双亲委托机制的原理,在执行热修复代码后,会先去加载patch.dex中的类,从而避免再加载有bug的类,达到热修复的目的