文章主要內(nèi)容為:
1.android的dex加載流程
2.利用DexClassLoader手動實現(xiàn)一個簡單版的熱修復
- 首先我們來了解一下什么是dex
在android虛擬機里面是無法直接運行.class文件,android會將所有的.class文件轉(zhuǎn)換成一個.dex文件距糖,然后通過DexClassLoader來加載.dex - dexElements數(shù)組是什么
是所有dex文件在系統(tǒng)內(nèi)存中的表現(xiàn)形式 - DexClassLoader實現(xiàn)熱修復的方式
從服務器端下載下來你的修復好的dex文件,利用代碼在dexElements數(shù)組中將修復好的dex文件插到有bug的dex文件前面 - DexClassLoader實現(xiàn)熱修復的原理
首先將dex文件移動到odex的緩存目錄(data/user/包名/app_odex),然后通過反射BaseDexClassLoader醋粟,找到BaseDexClassLoader下的pathList骤素,pathList下面有個dexElements數(shù)組力麸;分別將系統(tǒng)的dexElements數(shù)組和dex文件的dexElements數(shù)組創(chuàng)建出來磁滚,然后將dex的dexElements數(shù)組插入到系統(tǒng)的dexElements數(shù)組前面,最后將新的dexElements數(shù)組賦值到系統(tǒng)的dexElements中
好的擦酌,我們接下來用代碼來實現(xiàn)熱修復核心代碼
- 首先將手機儲存路徑下的dex文件移動到odex緩存目錄下
private void fix() {
File filesDir = this.getDir("odex", Context.MODE_PRIVATE);
String name = "out.dex";
String filePath = new File(filesDir, name).getAbsolutePath();
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
InputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream(new File(Environment.getExternalStorageDirectory(), name));
os = new FileOutputStream(filePath);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
//這里是修復dex的工具類,下面會寫到
FixUtils.loadDex(this);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 現(xiàn)在開始寫dex融合的工具類
public class FixUtils {
private static HashSet<File> loadDexList = new HashSet<File>();
public static void loadDex(Context context) {
if (context == null) {
return;
}
//將odex文件夾下的所有.dex文件存放到loadDexList下
File filesDir = context.getDir("odex", Context.MODE_PRIVATE);
File[] listFiles=filesDir.listFiles();
for (File file : listFiles) {
if(file.getName().startsWith("classes")||file.getName().endsWith(".dex")){
loadDexList.add(file);
}
}
//創(chuàng)建一個odex的緩存目錄
String optimizeDir = filesDir.getAbsolutePath() + File.separator + "cache_dex";
File fopt = new File(optimizeDir);
if (!fopt.exists()) {
fopt.mkdirs();
}
//遍歷list下所有的.dex文件 將.dex文件插入到系統(tǒng)的dexElements前面
for (File dex : loadDexList) {
DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(), fopt.getAbsolutePath(), null, context.getClassLoader());
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
try {
//利用反射創(chuàng)建系統(tǒng)的ClassLoader
Class baseDexClazzLoader=Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListFiled=baseDexClazzLoader.getDeclaredField("pathList");
pathListFiled.setAccessible(true);
Object pathListObject = pathListFiled.get(pathClassLoader);
Class systemPathClazz=pathListObject.getClass();
Field systemElementsField = systemPathClazz.getDeclaredField("dexElements");
systemElementsField.setAccessible(true);
Object systemElements=systemElementsField.get(pathListObject);
//利用反射創(chuàng)建自己的ClassLoader
Class myDexClazzLoader=Class.forName("dalvik.system.BaseDexClassLoader");
Field myPathListFiled=myDexClazzLoader.getDeclaredField("pathList");
myPathListFiled.setAccessible(true);
Object myPathListObject =myPathListFiled.get(classLoader);
Class myPathClazz=myPathListObject.getClass();
Field myElementsField = myPathClazz.getDeclaredField("dexElements");
myElementsField.setAccessible(true);
Object myElements=myElementsField.get(myPathListObject);
//將自己的ClassLoader的dexElements插入到系統(tǒng)的dexElements前面
Class<?> sigleElementClazz = systemElements.getClass().getComponentType();
int systemLength = Array.getLength(systemElements);
int myLength = Array.getLength(myElements);
int newSystenLength = systemLength + myLength;
//新建一個空的dexElements菠劝,長度為系統(tǒng)的dexElements長度+自己.dex文件的dexElements長度
Object newElementsArray = Array.newInstance(sigleElementClazz, newSystenLength);
//先將自己的.dex文件的dexElements插入到上面新建的dexElements里面赊舶,然后再將系統(tǒng)的dexElements插入進來
//這樣就實現(xiàn)了先加載修復好的dex的,然后在加載系統(tǒng)的dex
for (int i = 0; i < newSystenLength; i++) {
if (i < myLength) {
Array.set(newElementsArray, i, Array.get(myElements, i));
}else {
Array.set(newElementsArray, i, Array.get(systemElements, i - myLength));
}
}
//插入完成赶诊,將上面新建的dexElements賦值給系統(tǒng)的dexElements
Field elementsField=pathListObject.getClass().getDeclaredField("dexElements");
elementsField.setAccessible(true);
elementsField.set(pathListObject,newElementsArray);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 修復代碼已經(jīng)寫完了笼平,我們可以先創(chuàng)建一個修復前的測試類
public class Test {
public void testFix(Context context){
Toast.makeText(context, "修復前", Toast.LENGTH_SHORT).show();
}
}
然后運行到手機上
- 在創(chuàng)建一個修復之后的測試類
public class Test {
public void testFix(Context context){
Toast.makeText(context, "修復后", Toast.LENGTH_SHORT).show();
}
}
- 現(xiàn)在需要將修復后的test.java文件編譯成.dex,在android sdk的tool下舔痪,有一個dx工具可以使用寓调,將修復好的Test.java拿出來,使用dx工具編譯成out.dex锄码,然后將out.dex放到手機儲存卡下面夺英,第一次進入的時候,先運行上面寫的fix()方法滋捶,就能夠成功實現(xiàn)熱修復了