Android的在线热更新方案

Android的在线热更新方案,第1张

Android的在线热更新方案 Android的在线热更新方案_自实现

01.参考文章:https://blog.csdn.net/qq_39799899/article/details/102478355
02.Java层修复(dex文件覆盖):本文中使用
03.Native层修复:实例:https://blog.csdn.net/u013132758/article/details/96644721


1、创建工具类FixDexUtil:

注意:设置补丁文件存放位置REPAIR_FILE_PATH

public class FixDexUtil {

    //这下面两个属性可自己修改
    private final static String REPAIR_FILE_NAME = "BugClass";  //修复文件名
    private final static String REPAIR_FILE_PATH = "fix";      //修复文件路径(默认初始路径为根目录)

    private static final String DEX_SUFFIX = ".dex";
    private static final String APK_SUFFIX = ".apk";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    private static final String DEX_DIR = "odex";
    private static final String OPTIMIZE_DEX_DIR = "optimize_dex";
    private static HashSet loadedDex = new HashSet<>();

    static {
        loadedDex.clear();
    }

    
    public static void startRepair(final Context context) {
        File externalStorageDirectory = context.getExternalCacheDir();
        // 遍历所有的修复dex , 因为可能是多个dex修复包
        File fileDir = externalStorageDirectory != null ?
                new File(externalStorageDirectory, FixDexUtil.REPAIR_FILE_PATH) :
                new File(context.getFilesDir(), FixDexUtil.DEX_DIR);// data/user/0/包名/files/odex(这个可以任意位置)
        if (!fileDir.exists()) {//如果目录不存在就创建所有目录,这里需要添加权限
            fileDir.mkdirs();
        }
        if (FixDexUtil.isGoingToFix(context)) {
            FixDexUtil.loadFixedDex(context, context.getExternalCacheDir());
            Log.i("GT_", "正在修复");
        }
    }


    
    public static void loadFixedDex(Context context) {
        loadFixedDex(context, null);
    }

    
    public static void loadFixedDex(Context context, File patchFilesDir) {
        // dex合并之前的dex
        doDexInject(context, loadedDex);
    }

    
    public static boolean isGoingToFix(@NonNull Context context) {
        boolean canFix = false;
        File externalStorageDirectory = context.getExternalCacheDir();

        // 遍历所有的修复dex , 因为可能是多个dex修复包
        File fileDir = externalStorageDirectory != null ?
                new File(externalStorageDirectory, REPAIR_FILE_PATH) :
                new File(context.getFilesDir(), DEX_DIR);// data/data/包名/files/odex(这个可以任意位置)

        File[] listFiles = fileDir.listFiles();
        if (listFiles != null) {
            for (File file : listFiles) {
                if (file.getName().startsWith(REPAIR_FILE_NAME) &&
                        (file.getName().endsWith(DEX_SUFFIX)
                                || file.getName().endsWith(APK_SUFFIX)
                                || file.getName().endsWith(JAR_SUFFIX)
                                || file.getName().endsWith(ZIP_SUFFIX))) {

                    loadedDex.add(file);// 存入集合
                    //有目标dex文件, 需要修复
                    canFix = true;
                }
            }
        }
        return canFix;
    }

    private static void doDexInject(Context appContext, HashSet loadedDex) {
        String optimizeDir = appContext.getFilesDir().getAbsolutePath() +
                File.separator + OPTIMIZE_DEX_DIR;
        // data/data/包名/files/optimize_dex(这个必须是自己程序下的目录)

        File fopt = new File(optimizeDir);
        if (!fopt.exists()) {
            fopt.mkdirs();
        }
        try {
            // 1.加载应用程序dex的Loader
            PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
            for (File dex : loadedDex) {
                // 2.加载指定的修复的dex文件的Loader
                DexClassLoader dexLoader = new DexClassLoader(
                        dex.getAbsolutePath(),// 修复好的dex(补丁)所在目录
                        fopt.getAbsolutePath(),// 存放dex的解压目录(用于jar、zip、apk格式的补丁)
                        null,// 加载dex时需要的库
                        pathLoader// 父类加载器
                );
                // 3.开始合并
                // 合并的目标是Element[],重新赋值它的值即可

                

                //3.1 准备好pathList的引用
                Object dexPathList = getPathList(dexLoader);
                Object pathPathList = getPathList(pathLoader);
                //3.2 从pathList中反射出element集合
                Object leftDexElements = getDexElements(dexPathList);
                Object rightDexElements = getDexElements(pathPathList);
                //3.3 合并两个dex数组
                Object dexElements = combineArray(leftDexElements, rightDexElements);

                // 重写给PathList里面的Element[] dexElements;赋值
                Object pathList = getPathList(pathLoader);// 一定要重新获取,不要用pathPathList,会报错
                setField(pathList, pathList.getClass(), "dexElements", dexElements);

            }
            Toast.makeText(appContext, "修复完成", Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    
    private static void setField(Object obj, Class cl, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cl.getDeclaredField(field);
        declaredField.setAccessible(true);
        declaredField.set(obj, value);
    }

    
    private static Object getField(Object obj, Class cl, String field) throws NoSuchFieldException, IllegalAccessException {
        Field localField = cl.getDeclaredField(field);
        localField.setAccessible(true);
        return localField.get(obj);
    }


    
    private static Object getPathList(Object baseDexClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        return getField(baseDexClassLoader, Class.forName("dalvik.system.baseDexClassLoader"), "pathList");
    }

    
    private static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException {
        return getField(pathList, pathList.getClass(), "dexElements");
    }

    
    private static Object combineArray(Object arrayLhs, Object arrayRhs) {
        Class clazz = arrayLhs.getClass().getComponentType();
        int i = Array.getLength(arrayLhs);// 得到左数组长度(补丁数组)
        int j = Array.getLength(arrayRhs);// 得到原dex数组长度
        int k = i + j;// 得到总数组长度(补丁数组+原dex数组)
        Object result = Array.newInstance(clazz, k);// 创建一个类型为clazz,长度为k的新数组
        System.arraycopy(arrayLhs, 0, result, 0, i);
        System.arraycopy(arrayRhs, 0, result, i, j);
        return result;
    }

}

2、获取原始Apk和获取补丁Apk:

01.获取原始Apk

02.获取补丁Class文件

03.Class文件转换为dex文件

 .dx --dex --no-strict --output=C:Users...DesktopdexBugClass.dex C:Users...DesktopdexBugClass.class

04.adb命令push生成的dex文件到手机指定位置

adb push C:Users...DesktoptestBugClass.dex /sdcard/Android/data/com.gsls.thermalremediation/cache/fix

3、项目下载地址: https://download.csdn.net/download/huye930610/37076176
4、存在的问题:
  • 01.无法成功修复Release版本的Apk(加上了混淆)
    混淆会使修复失败(Apk签名没有影响)

    尝试:使用重复混淆的方式------Project的build.gradle中添加依赖

使用重复混淆的方式------proguard文件中设置复用混淆规则

结果:没什么卵用!!!!!

  • 02.项目中使用了Dagger2注解,无法获取到Class文件

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5075474.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-16
下一篇 2022-11-16

发表评论

登录后才能评论

评论列表(0条)

保存