android原生熱修復流程和原理分析實現
阿新 • • 發佈:2018-11-05
首先apk就是一個壓縮檔案,解壓apk檔案的內容如下圖:
安卓原生熱修復主要原理圖和流程圖如下,我花了好長時間才繪好,中間改了好幾次,應該來說是很直觀明白的,其中有截取了BaseDexClassLoader的關鍵原始碼,還有DexPathList的原始碼
a.現將打包好的dex檔案傳入手機中。
b.開始擼程式碼(主介面)
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_fix:
startHotFix();
break ;
case R.id.btn_test:
testInject();
break;
}
}
//開始熱修復
private void startHotFix() {
//pathClassLoader的特點:只能載入本工程目錄下的dex檔案。下面是原始碼的註釋
//operates on a list of files and directories in the local file system, but does not attempt to
//load classes from the network
//建立SD卡目錄存放熱修復的dex
File srcDexFolder = initSrcDexFile();
//從SD卡將下載好的dex複製到存放目錄
//建立要在工程目錄存放dex的目錄
File destFile = initDestDexFile();
//開始將剛下載的dex包複製到工程目錄下載面
copyDex2DestFolder(srcDexFolder, destFile);
//開始合併dex
try {
assembleDex(destFile);
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "startHotFix: =異常=" + e.toString());
}
}
//初始化搬遷目的dex目錄
private File initDestDexFile(){
File file = getDir(folderName, MODE_PRIVATE);
//複製之前,判斷之前熱修復檔案是否存在
File destFile = new File(file.getAbsolutePath());
if (!destFile.exists()) {
destFile.mkdirs();
} else {
//清空殘留的dex
File[] list = destFile.listFiles();
if (list != null && list.length > 0) {
for (File fileContent : list) {
Log.i(TAG, "startHotFix:要是刪除的:== " + fileContent.getAbsolutePath());
fileContent.delete();
}
}
}
Log.i(TAG, "startHotFix: =path2=" + destFile.getAbsolutePath());
return destFile;
}
//預備熱修復的源目錄
private File initSrcDexFile() {
String downLoadedDex = getExternalCacheDir().getAbsolutePath()
+ File.separatorChar + folderName;
File srcDexFolder = new File(downLoadedDex);
if (!srcDexFolder.exists()) {
srcDexFolder.mkdirs();
} else {
//測試裡面有幾個剛下載的dex包
File[] files = srcDexFolder.listFiles();
if (files != null && files.length > 0) {
for (File file : files) {
Log.i(TAG, "startHotFix: =dexPath=" + file.getAbsolutePath());
}
}
}
Log.i(TAG, "startHotFix: =path1=" + srcDexFolder.getAbsolutePath());
return srcDexFolder;
}
//載入新dex,插入原有dex之前
private void assembleDex(File destFile) throws Exception {
//利用應用本身pathClassLoader載入應用的dex
/**
* his method should be overridden by class loader implementations that
* follow the delegation model for loading classes, and will be invoked by
* 父類委託,所以要強轉
* pathClassLoader extends BaseDexClassLoader
*/
PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();
File dexOutputDir = getCodeCacheDir();//8.0原始碼推薦
File[] files = destFile.listFiles();
//獲取app的libs路徑
String librarySearchPath = getFileStreamPath("libs").getAbsolutePath();
if (files != null && files.length > 0) {
for (File file : files) {
//一 載入new dex
DexClassLoader dexClassLoader = new DexClassLoader(
file.getAbsolutePath(), //String dexPath,
dexOutputDir.getAbsolutePath(),//String optimizedDirectory,
null, //String librarySearchPath,
getClassLoader() //ClassLoader parent
);
//1.獲取new dex載入中 Pahtlist
Object newpathList = reflectPathList(dexClassLoader, "pathList");
//2.獲取 new dex 中的dexElements
Object newDexElements = reflectDexElements(newpathList, newpathList);
//二 載入old dex
//1.獲取 old dex中的PathList
Object oldPathList = reflectPathList(pathClassLoader, "pathList");
//2.獲取 old dex中的dexElements
Object oldDexElements = reflectDexElements(oldPathList, oldPathList);
//三 將新的dex 插入到舊dexElements中去
Object fixedElement = injectElements(newDexElements, oldDexElements);
//四 重新設定pathList的Element[] dexElements
setFixedElements(reflectPathList(pathClassLoader, "pathList"), fixedElement);
}
} else {
Log.i(TAG, "assembleDex: =為空=");
}
}
//反射得到classLoader中的成員DexPathList pathList
private Object reflectPathList(Object object, String field) throws Exception {
//錯誤的反射
/*BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader();
Class<? extends BaseDexClassLoader> aClass = classLoader.getClass();
//pathList在pathClassLoader中為空,在父類BaseDexClassLoader
Field dexPathList = aClass.getDeclaredField("pathList");
dexPathList.setAccessible(true);
Log.i(TAG, "assembleDex: =name="+dexPathList.getName());*/
//報錯原因如下:(被藏起來了,只反射到子類)
//java.lang.NoSuchFieldException: No field pathList in class dalvik/system/PathClassLoader;
//正確的反射
Class<?> baseClazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathList = baseClazz.getDeclaredField(field);
pathList.setAccessible(true);
Log.i(TAG, "assembleDex: =反射正常:=" + pathList.getName());
return pathList.get(object);
}
//反射得到pathList中的成員Element[] dexElements
private Object reflectDexElements(Object belongWho, Object pathList) throws Exception {
Class<?> aClass = pathList.getClass();
Field dexElements = aClass.getDeclaredField("dexElements");
dexElements.setAccessible(true);
return dexElements.get(belongWho);
}
//兩個資料夾複製
private void copyDex2DestFolder(@NonNull File sreFolder, @NonNull File destFolder) {
dexList.clear();
File[] filesList = sreFolder.listFiles();
if (filesList != null && filesList.length > 0) {
for (File file : filesList) {
//開始複製
if (file.getName().endsWith(".dex")) {
copyFile(file, destFolder);
}
}
//複製完成之後,測試是否複製成功
testCopyResult(destFolder);
} else {
Log.i(TAG, "copyDex2DestFolder: =複製內容為空=");
}
}
//單個檔案複製,一頓流操作
private void copyFile(@NonNull File file, @NonNull File destFolder) {
String name = file.getName();
Log.i(TAG, "copyFile: =name=" + name);
String destPath = destFolder.getAbsolutePath() + File.separator + name;
File destFile = new File(destPath);
try {
FileOutputStream outs = new FileOutputStream(destFile);
FileInputStream ins = new FileInputStream(file);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = ins.read(buffer)) != -1) {
outs.write(buffer, 0, len);
}
outs.close();
ins.close();
} catch (Exception e) {
e.printStackTrace();
}
}
核心程式碼
//替換掉pathList原有的陣列
private void setFixedElements(Object pathList, Object fixedElement) throws Exception {
Class<?> aClass = pathList.getClass();
Field dexElements = aClass.getDeclaredField("dexElements");
dexElements.setAccessible(true);
dexElements.set(pathList, fixedElement);
}
//插入原有陣列中去
private Object injectElements(@NonNull Object newDexElements, @NonNull Object oldDexElements) {
//獲取陣列中存放型別
Class<?> componentType = newDexElements.getClass().getComponentType();
if (componentType != null) {
int lengthNew = Array.getLength(newDexElements);
int lengthOld = Array.getLength(oldDexElements);
int totalLength = lengthNew + lengthOld;
Log.i(TAG, "injectElements: =陣列長度=lengthNew" + lengthNew + "||lengthOld" + lengthOld);
Object fixedElement = Array.newInstance(componentType, totalLength);
for (int i = 0; i < totalLength; ++i) {
if (i < lengthNew) {
Object element = Array.get(newDexElements, i);
Array.set(fixedElement, i, element);
} else {
Object element = Array.get(oldDexElements, i - lengthNew);
Array.set(fixedElement, i, element);
}
}
return fixedElement;
}
return null;
}
點選熱修復按鈕日誌
點選測試插入是否成功日誌
總結:1.檢視安卓原始碼,理順classLoader類的關係,後面就一目瞭然,最有價值的還是第二張圖,上面都有原理和步驟
2.原始碼註釋很經典,都說的很清楚,甚至有的上面都有介紹了最新用法。