前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Android 热修复】热修复原理 ( 加载 Dex 文件到内存中 | DexClassLoader | PathClassLoader | 反射 Element[] dexElements )

【Android 热修复】热修复原理 ( 加载 Dex 文件到内存中 | DexClassLoader | PathClassLoader | 反射 Element[] dexElements )

作者头像
韩曙亮
发布2023-03-29 09:42:10
6670
发布2023-03-29 09:42:10
举报
文章被收录于专栏:韩曙亮的移动开发专栏

文章目录

一、加载 Dex 文件到内存中


【Android 热修复】热修复原理 ( 修复包 Dex 文件准备 | Dex 优化为 Odex | Dex 文件拷贝 | 源码资源 )

【Android 热修复】热修复原理 ( Dex 文件拷贝后续操作 | 外部存储空间权限申请 | 执行效果验证 | 源码资源 )

两篇博客中 , 准备了 Dex 修复包 , 并将修复包 /storage/emulated/0/update.dex

从外置存储空间拷贝到了应用内置存储空间

/data/user/0/kim.hsl.hotfix/app_odex/update.dex

目录中 ;

【Android 热修复】热修复原理 ( 类加载分析 | 分析 PathClassLoader 源码 | 分析 BaseDexClassLoader 源码 | 分析 PathDexList 源码 ) 博客中分析了类加载的原理 ;

现在开始将 Dex 文件加载到内存中 , 这里指的是要按照 Dex 文件的管理方式 , 加载到 BaseDexClassLoader 类的 DexPathList pathList 成员的 Element[] dexElements 成员数组中 ;

1、文件处理

修复包可能有多个, 如先后进行了多次修复 , 存在多个修复包 Dex 文件 , 这些 Dex 文件按照时间顺序进行放置 ;

之前已经将 SD 卡中的 /storage/emulated/0/update.dex 文件拷贝到了原应用内置存储空间 /data/user/0/kim.hsl.hotfix/app_odex/update.dex ;

获取 dex 文件所在的应用内置存储空间目录 : 获取 /data/user/0/kim.hsl.hotfix/app_odex/ 目录文件 , 调用如下方法 , 即可生成该文件 ;

应用的包名是 kim.hsl.hotfix , 调用上下文对象的 getDir 方法 , 会在名称前自动加上 " app_ " 前缀 ;

代码语言:javascript
复制
// /data/user/0/kim.hsl.hotfix/app_odex/ 目录文件
File filesDir = context.getDir("odex", Context.MODE_PRI);

获取所有 dex 修复包 : 修复包可能存在多个 , 获取该 /data/user/0/kim.hsl.hotfix/app_odex/ 目录下的所有文件 ;

代码语言:javascript
复制
// 获取 /data/user/0/kim.hsl.hotfix/app_odex/ 目录下的所有文件
File[] listFiles = filesDir.listFiles();

odex 缓存文件目录 : dex 文件需要优化为 odex 文件 , 期间需要一个缓存文件目录 , 这里任意设置一个应用内置存储空间目录即可 ;

代码语言:javascript
复制
// 缓存 odex 文件的目录 , 将 dex 优化为 odex 文件
String optimizedDir = filesDir.getAbsolutePath() + File.separator + "cache_odex";

文件过滤 : 系统打包都是 classes.dex , classes1.dex , classes2.dex 等文件 , 上传的更新包 update.dex 以 .dex 为结尾 , 以上面两个条件作为过滤的依据 , 以 " classes " 开头 , 或以 " .dex " 结尾 的文件就是我们需要加载的 dex 文件 ;

代码语言:javascript
复制
        // 过滤文件, 系统打包都是 classes.dex , classes1.dex , classes2.dex 等文件
        // 上传的更新包 update.dex 以 .dex 为结尾
        // 以上面两个条件作为过滤的依据
        for (File file : listFiles){
            if (file.getAbsolutePath().startsWith("classes") ||
                    file.getAbsolutePath().endsWith(".dex")){
            }
        }

2、加载修复包 Dex 到内存

将 /data/user/0/kim.hsl.hotfix/app_odex/ 目录中的文件加载到内存中 :

使用 DexClassLoader 将 /data/user/0/kim.hsl.hotfix/app_odex/ 目录中的 dex 文件加载到内存中 , 构造 DexClassLoader 类时 , 会自动将 dex 文件进行优化为 odex , 然后加载到上述 DexClassLoader 类的 DexPathList pathList 成员 的 Element[] dexElements 数组成员 中 ;

这个 DexClassLoader 是我们自己创建的类加载器 ;

代码语言:javascript
复制
// 将 dex 文件加载到内存中
// 该 DexClassLoader 是 BaseDexClassLoader 的子类
// BaseDexClassLoader 中有 DexPathList pathList 成员
// 构造该类时 , 会自动将 dex 文件进行优化为 odex , 然后加载到上述 DexPathList pathList 中
//
// 参数一 : Dex 文件路径
// 参数二 : 缓存路径, 指的是缓存 Odex 文件的目录
// 参数三 : Dex 中的 lib 库路径, 可以设置 null
// 参数四 : 上下文的 ClassLoader
DexClassLoader dexClassLoader = new DexClassLoader(
        file.getAbsolutePath(),
        optimizedDir,
        null,
        context.getClassLoader());

3、获取系统类加载器

系统的类加载器是 PathClassLoader , 该 PathClassLoader 是用于加载查找 Android 应用所有 dex 文件的类加载器 , 最终需要将上面获取的 dexClassLoader 中的 DexPathList pathList 插入到 PathClassLoader 中的 DexPathList pathList 成员中 ;

代码语言:javascript
复制
// 该 PathClassLoader 是用于加载查找 Android 应用所有 dex 文件的类加载器
// 将上面获取的 dexClassLoader 中的 DexPathList pathList
// 插入到 PathClassLoader 中的 DexPathList pathList 成员中
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();

4、反射获取系统的 Element[] dexElements

获取 DexPathList pathList 对象 : 通过反射获取 BaseDexClassLoader 类 , 然后反射获取 BaseDexClassLoader 中的 private final DexPathList pathList 字段 , 由于是私有成员字段 , 需要设置可访问性 ;

通过系统类加载器 , 获取系统的 PathClassLoader pathClassLoader 对象的 DexPathList pathList 成员 ;

获取 Element[] dexElements 数组 : 通过反射获取 DexPathList 类 , 然后反射获取 获取 DexPathList 类中的 private final Element[] dexElements 成员字段 , 由于是私有成员字段 , 需要设置可访问性 , 最后获取 DexPathList pathList 对象的 Element[] dexElements 成员 ;

代码示例 :

代码语言:javascript
复制
// 加载系统的 Element[] dexElements ---------------------------------------------

// 反射获取 BaseDexClassLoader 类对象
Class systemBaseDexClassLoaderClass =
        Class.forName("dalvik.system.BaseDexClassLoader");
// 反射获取 BaseDexClassLoader 中的 private final DexPathList pathList 字段
Field systemPathListField =
        systemBaseDexClassLoaderClass.getDeclaredField("pathList");
// 由于是私有成员字段 , 需要设置可访问性
systemPathListField.setAccessible(true);

// 获取系统的 PathClassLoader pathClassLoader 对象的
// private final DexPathList pathList 成员
Object systemPathListObject = systemPathListField.get(pathClassLoader);

// 获取 DexPathList 类
Class systemPathListClass = systemPathListObject.getClass();
// 获取 DexPathList 类中的 private final Element[] dexElements 成员字段
Field systemDexElementsField =
        systemPathListClass.getDeclaredField("dexElements");
// 由于是私有成员字段 , 需要设置可访问性
systemDexElementsField.setAccessible(true);

// 获取 DexPathList pathList 对象的 Element[] dexElements 成员
Object systemDexElementsObject =
        systemDexElementsField.get(systemPathListObject);
        
// 系统的 Element[] dexElements 加载完毕-----------------------------------------

5、反射获取自己加载的 修复包 Dex 的 Element[] dexElements

获取 DexPathList pathList 对象 : 通过反射获取 BaseDexClassLoader 类 , 然后反射获取 BaseDexClassLoader 中的 private final DexPathList pathList 字段 , 由于是私有成员字段 , 需要设置可访问性 ;

通过系统类加载器 , 获取系统的 PathClassLoader pathClassLoader 对象的 DexPathList pathList 成员 ;

获取 Element[] dexElements 数组 : 通过反射获取 DexPathList 类 , 然后反射获取 获取 DexPathList 类中的 private final Element[] dexElements 成员字段 , 由于是私有成员字段 , 需要设置可访问性 , 最后获取 DexPathList pathList 对象的 Element[] dexElements 成员 ;

代码示例 :

代码语言:javascript
复制
// 加载自己的 Element[] dexElements ---------------------------------------------

// 反射获取 BaseDexClassLoader 类对象
Class myBaseDexClassLoaderClass =
        Class.forName("dalvik.system.BaseDexClassLoader");
// 反射获取 BaseDexClassLoader 中的 private final DexPathList pathList 字段
Field myPathListField =
        myBaseDexClassLoaderClass.getDeclaredField("pathList");
// 由于是私有成员字段 , 需要设置可访问性
myPathListField.setAccessible(true);

// 获取系统的 PathClassLoader pathClassLoader 对象的
// private final DexPathList pathList 成员
Object myPathListObject = myPathListField.get(pathClassLoader);


// 获取 DexPathList 类
Class myPathListClass = myPathListObject.getClass();
// 获取 DexPathList 类中的 private final Element[] dexElements 成员字段
Field myDexElementsField =
        myPathListClass.getDeclaredField("dexElements");
// 由于是私有成员字段 , 需要设置可访问性
myDexElementsField.setAccessible(true);

// 获取 DexPathList pathList 对象的 Element[] dexElements 成员
Object myDexElementsObject = myDexElementsField.get(myPathListObject);

// 自己的 Element[] dexElements 加载完毕-----------------------------------------

二、本博客涉及代码


代码语言:javascript
复制
        // 修复包可能有多个, 如先后进行了多次修复 , 存在多个修复包 Dex 文件
        // 这些 Dex 文件按照时间顺序进行放置
        // 之前已经将 SD 卡中的 /storage/emulated/0/update.dex 文件拷贝到了
        // 原应用内置存储空间 /data/user/0/kim.hsl.hotfix/app_odex/update.dex

        // /data/user/0/kim.hsl.hotfix/app_odex/ 目录文件
        File filesDir = context.getDir("odex", Context.MODE_PRIVATE);

        // 获取 /data/user/0/kim.hsl.hotfix/app_odex/ 目录下的所有文件
        File[] listFiles = filesDir.listFiles();

        // 缓存 odex 文件的目录 , 将 dex 优化为 odex 文件
        String optimizedDir = filesDir.getAbsolutePath() + File.separator + "cache_odex";

        // 过滤文件, 系统打包都是 classes.dex , classes1.dex , classes2.dex 等文件
        // 上传的更新包 update.dex 以 .dex 为结尾
        // 以上面两个条件作为过滤的依据
        for (File file : listFiles){
            if (file.getAbsolutePath().startsWith("classes") ||
                    file.getAbsolutePath().endsWith(".dex")){

                // 将 dex 文件加载到内存中
                // 该 DexClassLoader 是 BaseDexClassLoader 的子类
                // BaseDexClassLoader 中有 DexPathList pathList 成员
                // 构造该类时 , 会自动将 dex 文件进行优化为 odex , 然后加载到上述 DexPathList pathList 中
                //
                // 参数一 : Dex 文件路径
                // 参数二 : 缓存路径, 指的是缓存 Odex 文件的目录
                // 参数三 : Dex 中的 lib 库路径, 可以设置 null
                // 参数四 : 上下文的 ClassLoader
                DexClassLoader dexClassLoader = new DexClassLoader(
                        file.getAbsolutePath(),
                        optimizedDir,
                        null,
                        context.getClassLoader());

                // 该 PathClassLoader 是用于加载查找 Android 应用所有 dex 文件的类加载器
                // 将上面获取的 dexClassLoader 中的 DexPathList pathList
                // 插入到 PathClassLoader 中的 DexPathList pathList 成员中
                PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();

                // BaseDexClassLoader 中的 DexPathList pathList 是 private 私有的
                // 无法直接获取
                // 需要使用反射机制获取该 Dex 数组
                // 拿到 PathClassLoader (继承 BaseDexClassLoader 类) 对象后
                // 先使用反射机制获取 private final DexPathList pathList 成员
                // 然后再次通过反射 , 获取 DexPathList 中的 private final Element[] dexElements 成员


                try {
                    // 加载系统的 Element[] dexElements ---------------------------------------------
                    // 反射获取 BaseDexClassLoader 类对象
                    Class systemBaseDexClassLoaderClass =
                            Class.forName("dalvik.system.BaseDexClassLoader");
                    // 反射获取 BaseDexClassLoader 中的 private final DexPathList pathList 字段
                    Field systemPathListField =
                            systemBaseDexClassLoaderClass.getDeclaredField("pathList");
                    // 由于是私有成员字段 , 需要设置可访问性
                    systemPathListField.setAccessible(true);

                    // 获取系统的 PathClassLoader pathClassLoader 对象的
                    // private final DexPathList pathList 成员
                    Object systemPathListObject = systemPathListField.get(pathClassLoader);

                    // 获取 DexPathList 类
                    Class systemPathListClass = systemPathListObject.getClass();
                    // 获取 DexPathList 类中的 private final Element[] dexElements 成员字段
                    Field systemDexElementsField =
                            systemPathListClass.getDeclaredField("dexElements");
                    // 由于是私有成员字段 , 需要设置可访问性
                    systemDexElementsField.setAccessible(true);
                    // 获取 DexPathList pathList 对象的 Element[] dexElements 成员
                    Object systemDexElementsObject =
                            systemDexElementsField.get(systemPathListObject);
                    // 系统的 Element[] dexElements 加载完毕-----------------------------------------

                    // 上述反射的是系统的 PathClassLoader 的对象
                    // 下面开始反射在本次循环方法中加载的 DexClassLoader dexClassLoader

                    // 加载自己的 Element[] dexElements ---------------------------------------------
                    // 反射获取 BaseDexClassLoader 类对象
                    Class myBaseDexClassLoaderClass =
                            Class.forName("dalvik.system.BaseDexClassLoader");
                    // 反射获取 BaseDexClassLoader 中的 private final DexPathList pathList 字段
                    Field myPathListField =
                            myBaseDexClassLoaderClass.getDeclaredField("pathList");
                    // 由于是私有成员字段 , 需要设置可访问性
                    myPathListField.setAccessible(true);

                    // 获取系统的 PathClassLoader pathClassLoader 对象的
                    // private final DexPathList pathList 成员
                    Object myPathListObject = myPathListField.get(pathClassLoader);

                    // 获取 DexPathList 类
                    Class myPathListClass = myPathListObject.getClass();
                    // 获取 DexPathList 类中的 private final Element[] dexElements 成员字段
                    Field myDexElementsField =
                            myPathListClass.getDeclaredField("dexElements");
                    // 由于是私有成员字段 , 需要设置可访问性
                    myDexElementsField.setAccessible(true);
                    // 获取 DexPathList pathList 对象的 Element[] dexElements 成员
                    Object myDexElementsObject = myDexElementsField.get(myPathListObject);
                    // 自己的 Element[] dexElements 加载完毕-----------------------------------------

                    // 将系统 PathClassLoader pathClassLoader 的
                    // DexPathList pathList 对象的 Element[] dexElements 成员
                    // systemDexElementsObject
                    // 与
                    // 自己在程序中的 DexClassLoader dexClassLoader 的
                    // DexPathList pathList 对象的 Element[] dexElements 成员
                    // myDexElementsObject
                    // 进行融合
                    // 将 myDexElementsObject 插入到 systemDexElementsObject


                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        }

三、 源码资源


源码资源 :

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-04-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 一、加载 Dex 文件到内存中
    • 1、文件处理
      • 2、加载修复包 Dex 到内存
        • 3、获取系统类加载器
          • 4、反射获取系统的 Element[] dexElements
            • 5、反射获取自己加载的 修复包 Dex 的 Element[] dexElements
            • 二、本博客涉及代码
            • 三、 源码资源
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档