实现Andfix热更新

一.什么是热修复:

正常开发流程

image.png

热修复开发流程

image.png

热修复优势

实现Andfix热更新_第1张图片
image.png

Andfix的原理是,抽象类加载器classLoader的子类BaseDexClassLoader初始化pathList,pathList这个对象中存储着含.dex的dexElements数组,线上出现问题,提供将修复的类包装成.dex文件加入dexElements数组中前列,通过反射合并插桩pathList,最终修改了pathList中的dexElements数组对象。

下面演示编写bug,在线修复。

点击按钮 :10/0
public class ParamsSort {

    public void math(Context context) {
        int a = 10;
        int b = 1;
        Toast.makeText(context, "math >>> " + a / b, Toast.LENGTH_SHORT).show();
    }
}


1.通过服务端接口下载dex文件,拷贝到私有目录临时文件夹 odex

// classes2.dex ---> /storage/emulated/0/classes2.dex
    private void fixBug() {
        // 通过服务器接口下载dex文件,v1.3.3版本有某一个热修复dex包
        File sourceFile = new File(Environment.getExternalStorageDirectory(), Constants.DEX_NAME);

        // 目标路径:私有目录里的临时文件夹odex
        File targetFile = new File(getDir(Constants.DEX_DIR, Context.MODE_PRIVATE).getAbsolutePath()
                + File.separator + Constants.DEX_NAME);

        // 如果存在,比如之前修复过classes2.dex。清理
        if (targetFile.exists()) {
            targetFile.delete();
            Toast.makeText(this, "删除已存在的dex文件", Toast.LENGTH_SHORT).show();
        }

        try {
            // 复制修复包dex文件到app私有目录
            FileUitls.copyFile(sourceFile, targetFile);
            Toast.makeText(this, "复制dex文件完成", Toast.LENGTH_SHORT).show();
            // 加载热修复Dex文件
            FixDexUtils.loadFixedDex(this);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.拷贝完成后将需要修复的dex集合存入,主包情况不加入插桩队列:

 /**
     * 加载热修复的dex文件
     * @param context 上下文
     */
    public static void    loadFixedDex(Context context) {
        if (context == null) return;
        // Dex文件目录(私有目录中,存在之前已经复制过来的修复包)
        File fileDir = context.getDir(Constants.DEX_DIR, Context.MODE_PRIVATE);
        File[] listFiles = fileDir.listFiles();
        // 遍历私有目录中所有的文件
        for (File file : listFiles) {
            // 找到修复包,加入到集合
            if (file.getName().endsWith(Constants.DEX_SUFFIX) && !"classes.dex".equals(file.getName())) {
                loadedDex.add(file);
            }
        }

        // 模拟类加载器
        createDexClassLoader(context, fileDir);
    }

3.创建加载补丁的DexClassLoader

  private static void createDexClassLoader(Context context, File fileDir) {
        // 创建临时的解压目录(先解压到该目录,再加载java)
        String optimizedDir = fileDir.getAbsolutePath() + File.separator + "opt_dex";
        // 不存在就创建
        File fopt = new File(optimizedDir);
        if (!fopt.exists()) {
            // 创建多级目录
            fopt.mkdirs();
        }
        for (File dex : loadedDex) {
            // 每遍历一个要修复的dex文件,就需要插桩一次
            DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(),
                    optimizedDir, null, context.getClassLoader());
            hotfix(classLoader, context);
        }
    }

4.热修复:


    /**
     * 热修复
     * @param classLoader 自有的类加载器,加载了修复包的DexClassLoader
     * @param context 上下文
     */
    private static void hotfix(DexClassLoader classLoader, Context context) {
        // 获取系统PathClassLoader类加载器
        PathClassLoader pathLoader = (PathClassLoader) context.getClassLoader();

        try {
            // 获取自有的dexElements数组对象
            Object myDexElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(classLoader));

            // 获取系统的dexElements数组对象
            Object systemDexElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(pathLoader));

            // 合并成新的dexElements数组对象
            Object dexElements = ArrayUtils.combineArray(myDexElements, systemDexElements);

            // 通过反射再去获取   系统的pathList对象
            Object systemPathList = ReflectUtils.getPathList(pathLoader);

            // 重新赋值给系统的pathList属性  --- 修改了pathList中的dexElements数组对象
            ReflectUtils.setField(systemPathList, systemPathList.getClass(), dexElements);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

上面通过反射获取系统dexElements 和修复的dexElements然后合并数组,再通过反射获取pathList 去重新付值dexElements

public class ReflectUtils {

    /**
     * 通过反射获取某对象,并设置私有可访问
     *
     * @param obj   该属性所属类的对象
     * @param clazz 该属性所属类
     * @param field 属性名
     * @return 该属性对象
     */
    private static Object getField(Object obj, Class clazz, String field)
            throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException {
        Field localField = clazz.getDeclaredField(field);
        localField.setAccessible(true);
        return localField.get(obj);
    }

    /**
     * 给某属性赋值,并设置私有可访问
     *
     * @param obj   该属性所属类的对象
     * @param clazz 该属性所属类
     * @param value 值
     */
    public static void setField(Object obj, Class clazz, Object value)
            throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException {
        Field localField = clazz.getDeclaredField("dexElements");
        localField.setAccessible(true);
        localField.set(obj, value);
    }

    /**
     * 通过反射获取BaseDexClassLoader对象中的PathList对象
     *
     * @param baseDexClassLoader BaseDexClassLoader对象
     * @return PathList对象
     */
    public static Object getPathList(Object baseDexClassLoader)
            throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException, ClassNotFoundException {
        return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }

    /**
     * 通过反射获取BaseDexClassLoader对象中的PathList对象,再获取dexElements对象
     *
     * @param paramObject PathList对象
     * @return dexElements对象
     */
    public static Object getDexElements(Object paramObject)
            throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException {
        return getField(paramObject, paramObject.getClass(), "dexElements");
    }
}

你可能感兴趣的:(实现Andfix热更新)