09.源码阅读(从Android源码角度入手自己实现热修复)

简介: 首先我们要知道Activity是如何启动的,在文章https://www.jianshu.com/p/bd5208574430中我们已经看了Activity启动的源码,https://www.

首先我们要知道Activity是如何启动的,在文章https://www.jianshu.com/p/bd5208574430中我们已经看了Activity启动的源码,https://www.jianshu.com/p/40f436881390 ClassLoader加载类的源码,这里再简单回顾一下

img_48e5a2da4c2a2007770206859ff325bc.png
Activity加载流程.png

看了Activity的启动流程和ClassLoader加载类的方式之后,我们知道,当一个Activity要启动的时候,最终其实是通过从BaseDexClassLoader中的一个DexPathList类中的dexElements数组中取出来,反射得到对象返回的

那么当开发中出现了问题,比如一个Activity中发生了bug,我们要紧急修复这个问题,同时又不需要应用重启,该怎么做呢,热修复的解决方案有几个可选的阿里 AndFix(不再维护了,并且不支持8.0及以上,不建议使用),腾讯Tinker(需要重启才能生效)。不过今天我们不使用第三方的实现,只从源码方面找答案

关键点就在于dexElements这个数组,经过我们的分析,我们已经知道,所有的类的dexFile文件在应用启动时就已经被保存的这个数组中了,包括发生了错误的文件类,当类被加载的时候,会从前往后遍历这个数组,找到对应的class然后反射得到对象,那么我们的解决办法是,能否将正确的文件类插入到这个数组中,当这个类启动的时候,先加载到我们修复的正确的dexFile文件,从而达到修复的目的,要使用的技术就是反射

实现原理借鉴了腾讯的Tinker,不过Tinker核心思想是利用DexDiff算法对比差异生成Patch补丁包,将生成的差异dex文件插入dexElements,而我们做的却少了生成差分包的过程,是将整个dex文件插入替换,Tinker原理图如下:

img_19509cc91da8f5cf00205a82688e0ef7.png
20170630144819943.png

我们当前要实现的思路就是现上有一个发生bug的app有待修复,我们在线下生成一个修复后的apk,将其后缀改为.zip,然后解压打开,得到里边的classes.dex文件,客户端下载这个修复的dex到本地,然后将这个dex插入本地apk的dex数组中实现修复bug的功能。

构造方法执行,初始化dex文件的存储位置

public FixDexManager(Context context) {
        this.mContext = context;
        //获取系统能够访问的dex目录
        this.mDexDir = context.getDir("odex",Context.MODE_PRIVATE);
    }

遍历所有dex文件,存入集合中,这样做也是仿照了AndFix的处理方式,目录下可能存在多个dex文件,所以我们需要将他们都放入集合中,然后开始修复

public void loadFixDex() throws Exception{
        File[] dexFiles = mDexDir.listFiles();
        List<File> fixDexFiles = new ArrayList<>();
        for (File dexFile : dexFiles) {
            if (dexFile.getName().endsWith(".dex")){
                fixDexFiles.add(dexFile);
            }
        }
        fixDexFiles(fixDexFiles);
    }

进入fixDexFiles方法
加载到程序已经运行的dexElements数组,这个数组包括存在问题的类,然后通过BaseDexClassLoader加载得到我们修复后的dex文件中的dexElements数组,最终将这个正确的dexElements数组插入到程序已经运行的dexElements中,从而当程序要启动一个类的时候,会从数组中获取到正确的类,达到修复bug的目的

private void fixDexFiles(List<File> fixDexFiles) throws Exception{
        //1.先获取已经运行的dexElement
        ClassLoader applicationClassLoader = mContext.getClassLoader();
        //Element数组对象
        Object applicationDexElements = getDexElementByClassLoader(applicationClassLoader);

        File optimizedDirectory = new File(mDexDir,"odex");
        if (!optimizedDirectory.exists()){
            optimizedDirectory.mkdirs();
        }

        //修复
        for (File fixDexFile : fixDexFiles) {
            //参数:
            // String dexPath,  dex路径
            // File optimizedDirectory,
            // String librarySearchPath,  so文件位置
            // ClassLoader parent   父classloader
            ClassLoader fixDexClassLoader = new BaseDexClassLoader(
                    fixDexFile.getAbsolutePath(),//dex路径,必须要在应用目录下的odex文件中
                    optimizedDirectory,
                    null,
                    applicationClassLoader
            );

            Object fixDexElements = getDexElementByClassLoader(fixDexClassLoader);

            //3.将下载的dex插入到已经运行的dexElement的最前边,合并
            //applicationClassLoader 数组合并fixDexElements数组
            applicationDexElements = combineArray(fixDexElements, applicationDexElements);

            //把合并的数组注入到原来的类中 applicationClassLoader
            injectDexElements(applicationClassLoader, applicationDexElements);
        }
    }

通过反射再次获取到dexElements这个数组的Field,和它所在DexPathList的对象,注入即可

    //把dexElements注入到classLoader中
    private void injectDexElements(ClassLoader classLoader, Object dexElements) throws Exception{
        //先获取pathList
        Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathList = pathListField.get(classLoader);

        //获取pathList中的dexElements
        Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);

        //利用反射进行注入
        dexElementsField.set(pathList,dexElements);
    }

使用方法:
在Application启动的时候初始化并调用修复方法,这种实现方式有很大的问题,因为是将修复后的dex插入原来的dex数组,以保证加载类的时候可以加载到正确的类,那么这种情况下,如果当前bug页面已经加载出来,这时候再通过这种方式注入dex是不会起作用的,必须在启动bug类之前将dex注入,否则如果不重启,不会有效果。所以这种方式实现的热修复必须要重启生效。

    FixDexManager manager = new FixDexManager(this);
        //加载所有修复的dex包,第一次的时候会从服务器上下载到修复的dex包并放在我们制定的目录下,
        //第二次进入的时候,直接读取保存的文件进行修复
        try {
            manager.loadFixDex();

            File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"fix.apatch");
            if (file.exists()){
                try {
                    manager.fixDex(file.getAbsolutePath());
                    Toast.makeText(getApplicationContext(),"修复bug成功",Toast.LENGTH_SHORT).show();
                } catch (Exception e) {
                    Toast.makeText(getApplicationContext(),"修复bug失败",Toast.LENGTH_SHORT).show();
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

总结一下:
通过这种方式实现热修复,需要重启app,它的缺点也很明显,由于是将整个dex文件注入到原来dex数组中,会使app内存占用增大一倍左右,我这边亲测,原本占空间10.8M的app,注入新的dex后,内存占用变成了19.8M,可以考虑分包的方案解决这个问题,减少体积。

相关文章
|
16天前
|
XML Java Android开发
Android实现自定义进度条(源码+解析)
Android实现自定义进度条(源码+解析)
49 1
|
16天前
|
Java Android开发
Android反编译查看源码
Android反编译查看源码
21 0
|
27天前
|
移动开发 监控 安全
mPaaS常见问题之Android集成dexPatch热修复运行时候无法正常进行热更新如何解决
mPaaS(移动平台即服务,Mobile Platform as a Service)是阿里巴巴集团提供的一套移动开发解决方案,它包含了一系列移动开发、测试、监控和运营的工具和服务。以下是mPaaS常见问题的汇总,旨在帮助开发者和企业用户解决在使用mPaaS产品过程中遇到的各种挑战
34 0
|
3月前
|
算法 Java 定位技术
分享104个益智休闲安卓游戏源码,总有一款适合你
分享104个益智休闲安卓游戏源码,总有一款适合你
143 1
|
1月前
|
定位技术 API 数据库
基于Android的在线移动电子导航系统的研究与实现(论文+源码)_kaic
基于Android的在线移动电子导航系统的研究与实现(论文+源码)_kaic
|
1月前
|
搜索推荐 测试技术 定位技术
基于Android的自助导游系统的设计与实现(论文+源码)_kaic
基于Android的自助导游系统的设计与实现(论文+源码)_kaic
|
1月前
|
Java 关系型数据库 应用服务中间件
基于Android的人事管理系统设计与实现(论文+源码)_kaic
基于Android的人事管理系统设计与实现(论文+源码)_kaic
|
1月前
|
设计模式 测试技术 数据库
基于Android的食堂点餐APP的设计与实现(论文+源码)_kaic
基于Android的食堂点餐APP的设计与实现(论文+源码)_kaic
|
2月前
|
小程序 JavaScript Java
android电子班牌人脸识别系统源码
智慧校园云平台全套源码包含:电子班牌管理系统、成绩管理系统、考勤人脸刷卡管理系统、综合素养评价系统、请假管理系统、电子班牌发布系统、校务管理系统、小程序移动端、教师后台管理系统、SaaS运营云平台。
34 1
|
2月前
|
小程序 Java 数据挖掘
Java校园智慧管理云平台源码 小程序+android电子班牌系统
智慧校园技术架构 ❀后端:Java ❀框架:springboot ❀前端页面:vue +element-ui ❀小程序:小程序原生开发 ❀电子班牌:Java Android
34 0

推荐镜像

更多