關于熱修復的原理,其實網上有很多文章的講述。但是原理都是一致的,所以實現起來差異不是很大。
在Android的ClassLoader體系中,Android中加載類都是使用PatchClassLoader和DexClassLoader*,他們有什么區別呢?
其實很容易說明:
*DexClassLoader是可以用于加載apk文件或jar文件的。
*PathClassLoader只能加載dex格式文件。
一、Dex的加載。
無論是DexClassLoader還是PathClassLoader都是BaseDexClassLoader的子類,在BaseDexClassLoader初始化的時候就會加載dexPath下的classes.dex文件。然后放入DexPathList中
那么DexPathList是什么呢?我們一起看一看他的構造函數
原來是將我們傳入的DexPath,加載到當前的dexElements中去了。
這樣我們app里的dex文件就被加載到davlik里了,那么他是怎么去find的Class呢?
這樣的話,只要我們修改dexElements中的順序,將我們的patchjar放到數組的第一個,這樣就會加載我們patch.jar里面的類,而代替之前存有bug的類。
對,他的原理就是這么簡單。
二、修改順序的驗證。
我們根據這個來試驗一下,看看是否能夠成功。
失敗了,報錯了。我們根據異常信息追蹤一下
在校驗是否在同一個dex的時候做了一個判斷
if (!fromUnverifiedConstant &&
IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED))
那么這個CLASS_ISPREVERIFIED是什么時候設置的呢?虛擬機在把dex優化成odex的過程中會對該dex的所有class一個校驗。
那么他是怎么dvmVerifyClass 的呢?
校驗class的static 、private、 構造函數和虛函數。也就是說會根據構造函數或者靜態函數,私有函數。然后這個類會被打上CLASS_ISPREVERIFIED。
所以當我們的A.class調用到B.class的時候 只會在同一個dex中查找,如果dex不相同就會拋出來我們之前遇見的異常了。
那么怎么跳過校驗呢?
很簡單只要在我們的a.dex中調用b.dex的某一個類就可以了,這樣就防止類被打上CLASS_ISPREVERIFIED。
QQ 空間的做法是在所有類的構造函數加入了
if (ClassVerifier.PREVENT_VERIFY) {
System.out.println(AntilazyLoad.class); }
這樣當校驗class的時候會引用一個在不同dex的AntilazyLoad類。然后在啟動的時候AntilazyLoad的dex包要先加載進來,否則會找不到AntilazyLoad類的。還需要注意一點就是Application是不能加入這段代碼的,因為這樣就是在加載AntilazyLoad之前就使用了。
總結:
在ClassLoader中有一個存儲dex的數組dexElements,修改dexElements的順序可以決定優先選取哪個dex的class。這樣就可以實現熱修復的效果。但是dex在優化的過程中會進行class的校驗,給每一個class打上了一個CLASS_ISPREVERIFIED的標簽,在
調用的時候會根據該標簽判斷所在的class是否是同一個dex如果不是是會拋出異常導致程序停止。所以我們需要防止類被打上CLASS_ISPREVERIFIED。就需要在我們的class中調用另一個dex的class,所以我們選擇在構造函數中加上另一個dex的引用。之所以選擇構造函數是因為他不增加方法數,一個類即使沒有顯式的構造函數,也會有一個隱式的默認構造函數。
這就是熱修復的原理了,下一次我會和大家一起學習一下怎么樣根據熱修復的原理進行實現。