android原生热修复流程和原理分析实现

首先apk就是一个压缩文件,解压apk文件的内容如下图:
这里写图片描述

安卓原生热修复主要原理图和流程图如下,我花了好长时间才绘好,中间改了好几次,应该来说是很直观明白的,其中有截取了BaseDexClassLoader的关键源码,还有DexPathList的源码
这里写图片描述


a.现将打包好的dex文件传入手机中。
这里写图片描述
b.开始撸代码(主界面)
这里写图片描述

public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_fix:
                startHotFix();
                break;
            case R.id.btn_test:
                testInject();
                break;
        }
    }
 //开始热修复
    private void startHotFix() {
        //pathClassLoader的特点:只能加载本工程目录下的dex文件。下面是源码的注释
        //operates on a list of files and directories in the local file system, but does not attempt to
        //load classes from the network
        //创建SD卡目录存放热修复的dex
        File srcDexFolder = initSrcDexFile();
        //从SD卡将下载好的dex复制到存放目录
        //创建要在工程目录存放dex的目录
        File destFile = initDestDexFile();
        //开始将刚下载的dex包复制到工程目录下载面
        copyDex2DestFolder(srcDexFolder, destFile);
        //开始合并dex
        try {
            assembleDex(destFile);
        } catch (Exception e) {
            e.printStackTrace();
            Log.i(TAG, "startHotFix: =异常=" + e.toString());
        }
    }
 //初始化搬迁目的dex目录
    private File initDestDexFile(){
        File file = getDir(folderName, MODE_PRIVATE);
        //复制之前,判断之前热修复文件是否存在
        File destFile = new File(file.getAbsolutePath());
        if (!destFile.exists()) {
            destFile.mkdirs();
        } else {
            //清空残留的dex
            File[] list = destFile.listFiles();
            if (list != null && list.length > 0) {
                for (File fileContent : list) {
                    Log.i(TAG, "startHotFix:要是删除的:== " + fileContent.getAbsolutePath());
                    fileContent.delete();
                }
            }
        }
        Log.i(TAG, "startHotFix: =path2=" + destFile.getAbsolutePath());
        return destFile;
    }
    //预备热修复的源目录
    private File initSrcDexFile() {
        String downLoadedDex = getExternalCacheDir().getAbsolutePath()
                + File.separatorChar + folderName;
        File srcDexFolder = new File(downLoadedDex);
        if (!srcDexFolder.exists()) {
            srcDexFolder.mkdirs();
        } else {
            //测试里面有几个刚下载的dex包
            File[] files = srcDexFolder.listFiles();
            if (files != null && files.length > 0) {
                for (File file : files) {
                    Log.i(TAG, "startHotFix: =dexPath=" + file.getAbsolutePath());
                }
            }
        }
        Log.i(TAG, "startHotFix: =path1=" + srcDexFolder.getAbsolutePath());
        return srcDexFolder;
    }
//加载新dex,插入原有dex之前
    private void assembleDex(File destFile) throws Exception {
        //利用应用本身pathClassLoader加载应用的dex
        /**
         * his method should be overridden by class loader implementations that
         * follow the delegation model for loading classes, and will be invoked by
         * 父类委托,所以要强转
         * pathClassLoader extends BaseDexClassLoader
         */
        PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();
        File dexOutputDir = getCodeCacheDir();//8.0源码推荐
        File[] files = destFile.listFiles();
        //获取app的libs路径
        String librarySearchPath = getFileStreamPath("libs").getAbsolutePath();
        if (files != null && files.length > 0) {
            for (File file : files) {
                //一 加载new dex
                DexClassLoader dexClassLoader = new DexClassLoader(
                        file.getAbsolutePath(),  //String dexPath,
                        dexOutputDir.getAbsolutePath(),//String optimizedDirectory,
                        null, //String librarySearchPath,
                        getClassLoader() //ClassLoader parent
                );
                //1.获取new dex加载中 Pahtlist
                Object newpathList = reflectPathList(dexClassLoader, "pathList");
                //2.获取 new dex 中的dexElements
                Object newDexElements = reflectDexElements(newpathList, newpathList);

                //二 加载old dex
                //1.获取 old dex中的PathList
                Object oldPathList = reflectPathList(pathClassLoader, "pathList");
                //2.获取 old dex中的dexElements
                Object oldDexElements = reflectDexElements(oldPathList, oldPathList);
                //三 将新的dex 插入到旧dexElements中去
                Object fixedElement = injectElements(newDexElements, oldDexElements);
                //四 重新设置pathList的Element[] dexElements
                setFixedElements(reflectPathList(pathClassLoader, "pathList"), fixedElement);
            }
        } else {
            Log.i(TAG, "assembleDex: =为空=");
        }
    }

    //反射得到classLoader中的成员DexPathList pathList
    private Object reflectPathList(Object object, String field) throws Exception {
        //错误的反射
            /*BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader();
            Class<? extends BaseDexClassLoader> aClass = classLoader.getClass();
            //pathList在pathClassLoader中为空,在父类BaseDexClassLoader
            Field dexPathList = aClass.getDeclaredField("pathList");
            dexPathList.setAccessible(true);
            Log.i(TAG, "assembleDex: =name="+dexPathList.getName());*/
        //报错原因如下:(被藏起来了,只反射到子类)
        //java.lang.NoSuchFieldException: No field pathList in class dalvik/system/PathClassLoader;

        //正确的反射
        Class<?> baseClazz = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathList = baseClazz.getDeclaredField(field);
        pathList.setAccessible(true);
        Log.i(TAG, "assembleDex: =反射正常:=" + pathList.getName());
        return pathList.get(object);
    }

    //反射得到pathList中的成员Element[] dexElements
    private Object reflectDexElements(Object belongWho, Object pathList) throws Exception {
        Class<?> aClass = pathList.getClass();
        Field dexElements = aClass.getDeclaredField("dexElements");
        dexElements.setAccessible(true);
        return dexElements.get(belongWho);
    }

    //两个文件夹复制
    private void copyDex2DestFolder(@NonNull File sreFolder, @NonNull File destFolder) {
        dexList.clear();
        File[] filesList = sreFolder.listFiles();
        if (filesList != null && filesList.length > 0) {
            for (File file : filesList) {
                //开始复制
                if (file.getName().endsWith(".dex")) {
                    copyFile(file, destFolder);
                }
            }
            //复制完成之后,测试是否复制成功
            testCopyResult(destFolder);
        } else {
            Log.i(TAG, "copyDex2DestFolder: =复制内容为空=");
        }
    }

 //单个文件复制,一顿流操作
    private void copyFile(@NonNull File file, @NonNull File destFolder) {
        String name = file.getName();
        Log.i(TAG, "copyFile: =name=" + name);
        String destPath = destFolder.getAbsolutePath() + File.separator + name;
        File destFile = new File(destPath);
        try {
            FileOutputStream outs = new FileOutputStream(destFile);
            FileInputStream ins = new FileInputStream(file);
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = ins.read(buffer)) != -1) {
                outs.write(buffer, 0, len);
            }
            outs.close();
            ins.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

核心代码


    //替换掉pathList原有的数组
    private void setFixedElements(Object pathList, Object fixedElement) throws Exception {
        Class<?> aClass = pathList.getClass();
        Field dexElements = aClass.getDeclaredField("dexElements");
        dexElements.setAccessible(true);
        dexElements.set(pathList, fixedElement);
    }

    //插入原有数组中去
    private Object injectElements(@NonNull Object newDexElements, @NonNull Object oldDexElements) {
        //获取数组中存放类型
        Class<?> componentType = newDexElements.getClass().getComponentType();
        if (componentType != null) {
            int lengthNew = Array.getLength(newDexElements);
            int lengthOld = Array.getLength(oldDexElements);
            int totalLength = lengthNew + lengthOld;
            Log.i(TAG, "injectElements: =数组长度=lengthNew" + lengthNew + "||lengthOld" + lengthOld);
            Object fixedElement = Array.newInstance(componentType, totalLength);
            for (int i = 0; i < totalLength; ++i) {
                if (i < lengthNew) {
                    Object element = Array.get(newDexElements, i);
                    Array.set(fixedElement, i, element);
                } else {
                    Object element = Array.get(oldDexElements, i - lengthNew);
                    Array.set(fixedElement, i, element);
                }

            }
            return fixedElement;
        }
        return null;
    }

点击热修复按钮日志这里写图片描述

点击测试插入是否成功日志
这里写图片描述

总结:1.查看安卓源码,理顺classLoader类的关系,后面就一目了然,最有价值的还是第二张图,上面都有原理和步骤
2.源码注释很经典,都说的很清楚,甚至有的上面都有介绍了最新用法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值