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

文章主要內(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)熱修復了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末痛悯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子重窟,更是在濱河造成了極大的恐慌载萌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巡扇,死亡現(xiàn)場離奇詭異扭仁,居然都是意外死亡,警方通過查閱死者的電腦和手機厅翔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門乖坠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人知给,你說我怎么就攤上這事瓤帚。” “怎么了涩赢?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵戈次,是天一觀的道長。 經(jīng)常有香客問我筒扒,道長怯邪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任花墩,我火速辦了婚禮悬秉,結(jié)果婚禮上澄步,老公的妹妹穿的比我還像新娘。我一直安慰自己和泌,他們只是感情好村缸,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著武氓,像睡著了一般梯皿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上县恕,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天东羹,我揣著相機與錄音,去河邊找鬼忠烛。 笑死属提,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的美尸。 我是一名探鬼主播冤议,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼火惊!你這毒婦竟也來了求类?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤屹耐,失蹤者是張志新(化名)和其女友劉穎尸疆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惶岭,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡寿弱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了按灶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片症革。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鸯旁,靈堂內(nèi)的尸體忽然破棺而出噪矛,到底是詐尸還是另有隱情,我是刑警寧澤铺罢,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布艇挨,位于F島的核電站,受9級特大地震影響韭赘,放射性物質(zhì)發(fā)生泄漏缩滨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脉漏。 院中可真熱鬧苞冯,春花似錦、人聲如沸侧巨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刃泡。三九已至巧娱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烘贴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工撮胧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桨踪,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓芹啥,卻偏偏與公主長得像锻离,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子墓怀,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容