一、热修复
1、原理
热修复就是根据Android类加载的特性,通过反射改变类加载的源文件数组,让修复后补丁class优于有bug class加载,实现类替换效果,达到热修复的目的
2、手写热修复
下面热修复方案需要重启,因为已加载的类有缓存,不能再次加载
2.1、模拟bug并调用
首页onCreate中调用该方法BugTest.crash(),启动App则发生crash
public class BugTest {
public static void crash() {
throw new RuntimeException("Custom crash!");
}
}
2.2、反射实现热修复
- 获取
PathClassLoader - 通过
PathClassLoader获取其pathList字段 - 通过
pathList获取其dexElements字段,dexElements为Element数组,为类加载的源 - 通过
makePathElements方法和patchFile生成补丁数组 - 合并新数组并赋值
public class HotfixHelper {
public static void installPatch(Application application, File patchFile) {
if (!patchFile.exists()) {
return;
}
try {
//获取 PathClassLoader
ClassLoader classLoader = application.getClassLoader();
//获取 pathList
//位于 PathClassLoader 父类 BaseDexClassLoader 中:DexPathList pathList
Field pathListField = findField(classLoader, "pathList");
Object pathList = pathListField.get(classLoader);
//获取 dexElements
//位于 DexPathList 中:Element[] dexElements
Field dexElementsField = findField(pathList, "dexElements");
Object[] dexElements = (Object[]) dexElementsField.get(pathList);
//通过 makePathElements 方法和 patchFile 生成补丁数组
//位于 DexPathList 中
ArrayList<File> files = new ArrayList<>();
files.add(patchFile);
File codeCacheDir = application.getCodeCacheDir();
ArrayList<IOException> suppressedExceptions = new ArrayList<>();
Method makePathElements = findMethod(pathList, "makePathElements", List.class, File.class, List.class);
Object[] patchElements = (Object[]) makePathElements.invoke(null, files, codeCacheDir, suppressedExceptions);
//创建新的数组
Object[] newElements = (Object[]) Array.newInstance(patchElements.getClass().getComponentType(),
patchElements.length + dexElements.length);
//合并补丁数组和原有数组
System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);
System.arraycopy(dexElements, 0, newElements, patchElements.length, dexElements.length);
//更新
dexElementsField.set(pathList, newElements);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Field findField(Object instance, String name) {
Class<?> clazz = instance.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(name);
if (field != null) {
field.setAccessible(true);
return field;
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
clazz = clazz.getSuperclass();
}
throw new RuntimeException("Field not found!");
}
public static Method findMethod(Object instance, String name, Class<?>... parameterTypes) {
Class<?> clazz = instance.getClass();
while (clazz != null) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
if (method != null) {
method.setAccessible(true);
return method;
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
clazz = clazz.getSuperclass();
}
throw new RuntimeException("Method not found!");
}
}
2.3、生成热修复补丁
模拟修复bug,修复后输出日志,不在crash,编译代码,找到其class文件,
位于app/build/intermediates/javac/debug/classes
public class BugTest {
public static void crash() {
if (100 % 5 == 0) {
Log.d("--wh--", "Bug has been fixed.");
return;
}
throw new RuntimeException("Custom crash!");
}
}
cd到上述目录,通过dx生成补丁包,执行下面命令成功后生成补丁包:patch.jar
dx为Android SDK build-tools工具,需要配置环境变量
dx --dex --output=patch.jar com/shtech/testdemo/hotfix/BugTest.class
2.4、执行热修复
将补丁包拷贝到手机目录,在Application中执行热修复,完成修复
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
val file = File("${getExternalFilesDir("hotfix")}/patch.jar")
HotfixHelper.installPatch(this, file)
}