【Android 热修复】热修复原理 ( 合并两个 Element[] dexElements | 自定义 Application 加载 Dex 设置 | 源码资源 )

简介: 【Android 热修复】热修复原理 ( 合并两个 Element[] dexElements | 自定义 Application 加载 Dex 设置 | 源码资源 )

文章目录

一、合并两个 Element[] dexElements

二、 完整修复包加载工具类

三、 源码资源





一、合并两个 Element[] dexElements


在 【Android 热修复】热修复原理 ( 加载 Dex 文件到内存中 | DexClassLoader | PathClassLoader | 反射 Element[] dexElements ) 博客中已经将 系统加载的 Dex 文件对应的 Element[] dexElements 通过 PathClassLoader 类加载器获取到了 , 同时修复包对应 Dex 文件 Element[] dexElements 通过 DexClassLoader 类加载器获取到了 ;


下面开始将修复包对应的 Element[] dexElements 合并到系统 PathClassLoader 中的 Element[] dexElements 数组中 ;



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


将系统 PathClassLoader pathClassLoader 的 DexPathList pathList 对象的 Element[] dexElements 成员systemDexElementsObject


与


自己在程序中的 DexClassLoader dexClassLoader 的 DexPathList pathList 对象的 Element[] dexElements 成员myDexElementsObject


进行融合 , 将 myDexElementsObject 插入到 systemDexElementsObject ;



首先要获取 Dex 数组 , 但是 Element 类型无法引用 , 不是公开的 ;


先获取 Element 类型 , 调用对象的 .getClass().getComponentType() 获取 ;


// 获取 Dex 数组 , Element 类型无法引用 , 不是公开的
// 首先获取 Element 类型
// systemDexElementsObject
Class<?> elementClass = systemDexElementsObject.getClass().getComponentType();



获取两个 Element[] dexElements 数组的成员个数 ;


// 获取两个 Element[] dexElements 数组的成员个数
// 系统中的 PathClassLoader 中的 Element[] dexElements 数组大小
int systemDexCount = Array.getLength(systemDexElementsObject);
// 本应用中的 DexClassLoader 中的 Element[] dexElements 数组大小
int myDexCount = Array.getLength(myDexElementsObject);



使用 Array.newInstance 重新创建一个数组, 数组的长度是两个数组之和 ;


// 重新创建一个数组
// 类型 : Class<?> elementClass
// 长度 : systemDexCount + myDexCount
Object elementArray =
        Array.newInstance(elementClass, systemDexCount + myDexCount);



填充创建的数组 , 这里特别注意 , 数组中的元素的顺序很重要 , 一定要先放置修复包中的数组元素 , 然后放置应用自带的 Dex 数组内容 , 这个顺序一定不能乱 ;


// 填充数组内容, 这里特别注意 , 数组中的元素的顺序很重要 ,
// 同样类型的类 , 在多个 Dex 都存在 , 如果在前面的 Dex 中查找到了 , 就不再向后查找了
// 修复包的 Dex 要放在最前面 , 这样才能起到修复作用
// 先放置修复包 Dex
for(int i = 0; i < myDexCount; i ++){
    // 获取 myDexElementsObject 数组中的第 i 个元素
    // 放置到 elementArray 数组中的第 i 个元素位置
    Array.set(elementArray, i,
            Array.get(myDexElementsObject, i));
}
// 再放置系统 Dex
for(int i = 0; i < systemDexCount; i ++){
    // 获取 systemDexElementsObject 数组中的第 i 个元素
    // 放置到 elementArray 数组中的第 i + myDexCount 个元素位置
    Array.set(elementArray,
            i + myDexCount,
            Array.get(systemDexElementsObject, i));
}



最后 , 通过反射方法 , 将合并后的 elementArray 数组放置到 PathClassLoader 中的 Element[] dexElements 中 ;


// 通过反射方法
// 将合并后的 elementArray 数组放置到
// PathClassLoader 中的 Element[] dexElements 中
systemDexElementsField.set(systemPathListObject, elementArray);


注意 : 此时热修复还不能生效 , 需要进一步进行分包操作才可以 ;






二、 完整修复包加载工具类


package kim.hsl.hotfix;
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
public class FixDexUtils {
    /**
     * 加载 Dex 文件
     * @param context
     */
    public static void loadDex(Context context){
        // 修复包可能有多个, 如先后进行了多次修复 , 存在多个修复包 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(dexClassLoader);
                    // 获取 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
                    // 获取 Dex 数组 , Element 类型无法引用 , 不是公开的
                    // 首先获取 Element 类型
                    // systemDexElementsObject
                    Class<?> elementClass = systemDexElementsObject.getClass().getComponentType();
                    // 获取两个 Element[] dexElements 数组的成员个数
                    // 系统中的 PathClassLoader 中的 Element[] dexElements 数组大小
                    int systemDexCount = Array.getLength(systemDexElementsObject);
                    // 本应用中的 DexClassLoader 中的 Element[] dexElements 数组大小
                    int myDexCount = Array.getLength(myDexElementsObject);
                    Log.i("TAG", "systemDexCount = " + systemDexCount + " , myDexCount = " + myDexCount);
                    // 重新创建一个数组
                    // 类型 : Class<?> elementClass
                    // 长度 : systemDexCount + myDexCount
                    Object elementArray =
                            Array.newInstance(elementClass, systemDexCount + myDexCount);
                    // 填充数组内容, 这里特别注意 , 数组中的元素的顺序很重要 ,
                    // 同样类型的类 , 在多个 Dex 都存在 , 如果在前面的 Dex 中查找到了 , 就不再向后查找了
                    // 修复包的 Dex 要放在最前面 , 这样才能起到修复作用
                    // 先放置修复包 Dex
                    for(int i = 0; i < myDexCount; i ++){
                        // 获取 myDexElementsObject 数组中的第 i 个元素
                        // 放置到 elementArray 数组中的第 i 个元素位置
                        Array.set(elementArray, i,
                                Array.get(myDexElementsObject, i));
                    }
                    // 再放置系统 Dex
                    for(int i = 0; i < systemDexCount; i ++){
                        // 获取 systemDexElementsObject 数组中的第 i 个元素
                        // 放置到 elementArray 数组中的第 i + myDexCount 个元素位置
                        Array.set(elementArray,
                                i + myDexCount,
                                Array.get(systemDexElementsObject, i));
                    }
                    // 通过反射方法
                    // 将合并后的 elementArray 数组放置到
                    // PathClassLoader 中的 Element[] dexElements 中
                    systemDexElementsField.set(systemPathListObject, elementArray);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}






三、 源码资源


源码资源 :


GitHub 地址 : https://github.com/han1202012/HotFix

CSDN 源码快照 : 到下一篇博客下载 , 该快照目前还跑不起来 ;


注意 : 此时热修复还不能生效 , 需要进一步进行分包操作才可以 ;


目录
相关文章
|
存储 消息中间件 人工智能
【03】AI辅助编程完整的安卓二次商业实战-本地构建运行并且调试-二次开发改注册登陆按钮颜色以及整体资源结构熟悉-优雅草伊凡
【03】AI辅助编程完整的安卓二次商业实战-本地构建运行并且调试-二次开发改注册登陆按钮颜色以及整体资源结构熟悉-优雅草伊凡
363 3
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
992 56
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
683 3
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
602 3
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
724 0
|
12月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1974 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
12月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1261 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
12月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1330 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
开发工具 Android开发
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
1074 11
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
|
12月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
573 0

热门文章

最新文章