Android原理相关

179 阅读2分钟

一、热修复

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、反射实现热修复

  1. 获取PathClassLoader
  2. 通过PathClassLoader获取其pathList字段
  3. 通过pathList获取其dexElements字段,dexElementsElement数组,为类加载的源
  4. 通过makePathElements方法和patchFile生成补丁数组
  5. 合并新数组并赋值
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

dxAndroid 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)
}