DexClassLoader熱修復(fù)原理分析以及手動實現(xiàn)熱修復(fù)

文章主要內(nèi)容為:

1.android的dex加載流程
2.利用DexClassLoader手動實現(xiàn)一個簡單版的熱修復(fù)
  • 首先我們來了解一下什么是dex
    在android虛擬機(jī)里面是無法直接運(yùn)行.class文件,android會將所有的.class文件轉(zhuǎn)換成一個.dex文件,然后通過DexClassLoader來加載.dex
  • dexElements數(shù)組是什么
    是所有dex文件在系統(tǒng)內(nèi)存中的表現(xiàn)形式
  • DexClassLoader實現(xiàn)熱修復(fù)的方式
    從服務(wù)器端下載下來你的修復(fù)好的dex文件,利用代碼在dexElements數(shù)組中將修復(fù)好的dex文件插到有bug的dex文件前面
  • DexClassLoader實現(xiàn)熱修復(fù)的原理
    首先將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)熱修復(fù)核心代碼
  • 首先將手機(jī)儲存路徑下的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);
            }
            //這里是修復(fù)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插入進(jìn)來
                //這樣就實現(xiàn)了先加載修復(fù)好的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();
            }
        }
    }


}
  • 修復(fù)代碼已經(jīng)寫完了,我們可以先創(chuàng)建一個修復(fù)前的測試類
public class Test {
    public  void testFix(Context context){
        Toast.makeText(context, "修復(fù)前", Toast.LENGTH_SHORT).show();
    }
}

然后運(yùn)行到手機(jī)上

  • 在創(chuàng)建一個修復(fù)之后的測試類
public class Test {
    public  void testFix(Context context){
        Toast.makeText(context, "修復(fù)后", Toast.LENGTH_SHORT).show();
    }
}
  • 現(xiàn)在需要將修復(fù)后的test.java文件編譯成.dex,在android sdk的tool下,有一個dx工具可以使用,將修復(fù)好的Test.java拿出來,使用dx工具編譯成out.dex,然后將out.dex放到手機(jī)儲存卡下面,第一次進(jìn)入的時候,先運(yùn)行上面寫的fix()方法,就能夠成功實現(xiàn)熱修復(fù)了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容