Android熱修復之dex修復原理
首先有一個出Bug的類
然後在點擊按鈕事件裏面添加調用bug類的方法
模擬bug出現的場景。
再修復這個出bug類的方法
然後生成dex文件
dx命令
dx --dex --output=a.dex com\example\hellojnicallback\BugTest.class
要把BugTest的包路徑都複製過來,然後執行命令,執行成功後可以看到a.dex文件生成,重命名fix_dex.dex後把這個文件放到手機的sd卡上
在點擊單選按鈕2的時候調用修復的方法
這個主要是調用MergeDexUtil裏面的copyDexFileToAppAndFix方法
copyDexFileToAppAndFix方法主要是先把sdcard目錄上的dex文件copy到應用的目錄下
再調用mergeDex方法修復
104行~106行獲取我們APP和的補丁dex文件的BaseDexClassLoader裏面的pathList屬性,
如下,該屬性是個DexPathList類型。
108行~109行獲取DexPathList類裏面的dexElements屬性,該屬性是一個Element數組
111行則是把兩個數組和並,並且補丁裏面的Element數組在前面.現在看這些代碼會感覺有點懵逼
至於爲什麼這樣子,稍後解析原理時再說明緣由,瞭解了原理之後再來看這些代碼自然會茅塞頓開。
BaseDexClassLoader部分源碼
DexPathList部分源碼
getField主要是反射調用對象的屬性,setFieldValue主要是反射設置對象的屬性
combineArray方法主要是合併兩個數組
點擊2的單選按鈕
再次點擊helloworld控件後,顯示的是我們修復成功後的值
原理探祕篇:
首先一個類要能夠被使用,要被類加載器加載進來的。而android中主要用的是兩個類加載器DexClassLoader和PathClassLoader
適用場景:
DexClassLoader可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk;
PathClassLoader只能加載系統中已經安裝過的apk;
BootClassLoader:和java虛擬機中不同的是BootClassLoader是ClassLoader內部類,由java代碼實現而不是c++實現,是Android平臺上所有ClassLoader的最終parent,這個內部類是包內可見,所以我們沒法使用。
URLClassLoader:只能用於加載jar文件,但是由於 dalvik 不能直接識別jar,所以在 Android 中無法使用這個加載器。
平常我們安裝的app裏面的類加載基本都是PathClassLoader加載的
測試代碼
輸出
PathClassLoader 繼承BaseDexClassLoader ,加載類的方法在BaseDexClassLoader裏面,再查看BaseDexClassLoader方法
BaseDexClassLoader的findClass方法的54行,實際上加載findClass是DexPathList類的findClass去加載的,繼續看DexPathList的findClass方法
findClass的338行是DexPathList裏面的一個dexElements數組裏面去查找類的。
而dexElements數組裏面的Element是個啥呢
Element是DexPathList的一個內部類,裏面包含了DexFile對象
一般情況下,如果我們的app沒有分包的話,解壓apk文件,可以看到解壓的目錄下有個classes.dex文件,這個文件就是我們所有的源文件的class的合集後轉成dex的文件,至於爲什麼要把class文件轉成dex文件,因爲google工程師覺得class文件還是有很多冗餘信息,而且剛開始的時候手機內存本來就很小,於是把所有的class文件信息合併一個dex文件,並打包在apk中,
如果只有一個dex文件,在DexPathList裏面的一個dexElements的大小就爲1 ,有兩個則爲2 依次類推。
BaseDexClassLoader加載類如果要加載某個類,則從dexElements數組的依次查找類,如果查找到了,則返回。也就是前面我們把我們自己生成dex文件插到dexElements前面的原因,因爲查找到了我們的類,後面同樣的類就不會有機會加載到了。
總結:此技巧利用了android類加載器的加載機制從而實現了熱修復。
但是有個缺點是某個出bug的類加載過一次後,只能等app重新啓動後才能實現修復功能,因爲我們知道類加載過後就類信息就已經存在方法區中了,下次再用的話就不會重新再加載了一遍了。
做個試驗
在helloworld控件的點擊事件添加異常捕獲,以免app異常退出
在修復之前點擊helloworld控件,出現了異常。
在點擊2的單選按鈕修復。
修復後再點擊helloworld控件查看打印信息,依然沒有修復。
也就是說要app重新啓動才能達到修復效果
如果想要app不重新啓動的情況下達到修復效果,可以查看博客熱修復之AndFix探祕