文章目錄
- 一、合并兩個 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 原始碼快照 : 到下一篇博客下載 , 該快照目前還跑不起來 ;
注意 : 此時熱修復還不能生效 , 需要進一步進行分包操作才可以 ;
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/276648.html
標籤:其他
