一伯复、補丁 DEX 生成
- 修復(fù)問題后,修改 Java/Kotlin 代碼焦辅,編譯生成新的 .class 文件
javac -d ./build/classes FixClass.java # 生成 FixClass.class
- 生成新舊 DEX 文件
# 舊代碼生成 old.dex
d8 old_classes/*.class --output old.dex
# 新代碼生成 new.dex
d8 new_classes/*.class --output new.dex
- 差異對比(Diff)對比新舊 DEX 文件葛账,提取差異部分,使用二進制差分算法(如 BSDiff)生成補丁文件(.patch)
bsdiff old.dex new.dex patch.dex
# Tinker 示例
java -jar tinker-patch-cli.jar -old old.apk -new new.apk -output patch/
4.在本地模擬補丁加載流程,驗證修復(fù)效果
// 測試補丁加載邏輯(本地調(diào)試)
File patchFile = new File("本地路徑/patch.dex");
HotfixManager.applyPatch(patchFile);
二佳头、代碼中需提前實現(xiàn)的邏輯
- 補丁下載與存儲
// 偽代碼:下載補丁
File patchDir = new File(getFilesDir(), "hotfix_patches");
File patchFile = new File(patchDir, "patch_new.dex");
DownloadManager.download("https://example.com/patch.dex", patchFile);
- 補丁校驗與安全性驗證
// 校驗簽名和完整性
if (!SecurityUtils.verifySignature(patchFile, publicKey)) {
throw new SecurityException("補丁簽名無效");
}
if (!MD5Utils.check(patchFile, expectedMD5)) {
throw new IOException("補丁文件損壞");
}
3.使用 DexClassLoader 加載補丁鹰贵,并通過反射插入到 ClassLoade
// 加載補丁 DEX
File optimizedDir = new File(getCacheDir(), "dex_opt");
DexClassLoader loader = new DexClassLoader(
patchFile.getPath(), // 原始 DEX 路徑(如 /data/data/app/hotfix_patches/patch.dex)
optimizedDir.getPath(), // 優(yōu)化后的 ODEX 存儲目錄(如 /data/data/app/cache/dex_opt)
null,
getClassLoader()
);
// 反射注入補丁到 PathClassLoader
try {
Field pathListField = ClassLoader.class.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathList = pathListField.get(getClassLoader());
Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object[] dexElements = (Object[]) dexElementsField.get(pathList);
// 獲取補丁的 dexElements
Object patchElement = getDexElements(dexClassLoader); // 反射獲取補丁的 Element 數(shù)組
Object newElements = Array.newInstance(dexElements.getClass().getComponentType(), dexElements.length + 1);
System.arraycopy(patchElement, 0, newElements, 0, 1); // 補丁插入到最前面
System.arraycopy(dexElements, 0, newElements, 1, dexElements.length);
dexElementsField.set(pathList, newElements);
} catch (Exception e) {
e.printStackTrace();
}
- 資源與 Native 庫修復(fù)
// 合并插件資源
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, patchFile.getAbsolutePath());
Resources newResources = new Resources(assetManager, getResources().getDisplayMetrics(), getResources().getConfiguration());
// 替換全局 Resources 實例(需 Hook ContextImpl)
三、線上 App 運行時的流程
- 檢查補丁更新
- 下載補丁文件
- 加載并應(yīng)用補丁
- 重啟生效或即時生效
全量替換方案(Tinker):需重啟 App 或特定 Activity康嘉。
方法替換方案(AndFix):即時生效(通過替換 ArtMethod)
完整代碼
public class HotfixManager {
public static void applyPatch(Context context, File patchFile) {
// 1. 檢查補丁文件是否存在
if (patchFile == null || !patchFile.exists()) {
Log.e("Hotfix", "補丁文件不存在");
return;
}
// 2. 準備優(yōu)化目錄(dex_opt)
File optimizedDir = new File(context.getCodeCacheDir(), "dex_opt");
if (!optimizedDir.exists()) {
optimizedDir.mkdirs();
}
try {
// 3. 創(chuàng)建 DexClassLoader 加載補丁
DexClassLoader dexClassLoader = new DexClassLoader(
patchFile.getAbsolutePath(),
optimizedDir.getAbsolutePath(),
null,
context.getClassLoader()
);
// 4. 反射獲取應(yīng)用的 PathClassLoader 中的 dexElements
ClassLoader appClassLoader = context.getClassLoader();
Object appPathList = getPathList(appClassLoader);
Object[] appDexElements = getDexElements(appPathList);
// 5. 獲取補丁的 dexElements
Object patchPathList = getPathList(dexClassLoader);
Object[] patchDexElements = getDexElements(patchPathList);
// 6. 合并補丁和原 dexElements(將補丁插入到數(shù)組最前)
Object[] newDexElements = combineArray(patchDexElements, appDexElements);
// 7. 將合并后的數(shù)組注入到應(yīng)用的 ClassLoader
setDexElements(appPathList, newDexElements);
Log.i("Hotfix", "補丁加載成功");
} catch (Exception e) {
Log.e("Hotfix", "補丁加載失敗", e);
}
}
// 反射工具方法(需處理異常和兼容性)
private static Object getPathList(ClassLoader classLoader) throws Exception {
Field pathListField = ClassLoader.class.getDeclaredField("pathList");
pathListField.setAccessible(true);
return pathListField.get(classLoader);
}
private static Object[] getDexElements(Object pathList) throws Exception {
Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
return (Object[]) dexElementsField.get(pathList);
}
private static void setDexElements(Object pathList, Object[] elements) throws Exception {
Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
dexElementsField.set(pathList, elements);
}
private static Object[] combineArray(Object[] front, Object[] end) {
Object[] result = Arrays.copyOf(front, front.length + end.length);
System.arraycopy(end, 0, result, front.length, end.length);
return result;
}
}