文章目录
一、合并两个 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 源码快照 : 到下一篇博客下载 , 该快照目前还跑不起来 ;
注意 : 此时热修复还不能生效 , 需要进一步进行分包操作才可以 ;