熱修復系列之一----Android 熱修復原理篇及幾大方案比較
我們知道Android系統也是仿照java搞了一個虛擬機器,不過它不叫JVM,它叫Dalvik/ART VM他們還是有很大區別的(這是不是我們的重點,
點開是個拓展閱讀)。我們只需要知道,Dalvik/ART VM 虛擬機器載入類和資源也是要用到ClassLoader
,不過Jvm通過ClassLoader
載入的class位元組碼,而Dalvik/ART
VM通過ClassLoader
載入則是dex。
Android的類載入器分為兩種,PathClassLoader和DexClassLoader,兩者都繼承自BaseDexClassLoader
PathClassLoader
DexClassLoader程式碼位於libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java
BaseDexClassLoader程式碼位於libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java
- PathClassLoader
-
用來載入系統類和應用類
-
DexClassLoader
用來載入jar、apk、dex檔案.載入jar、apk也是最終抽取裡面的Dex檔案進行載入.
2.熱修復機制
熱修復就是利用dexElements
的順序來做文章,當一個補丁的patch.dex放到了dexElements
的第一位,那麼當載入一個bug類時,發現在patch.dex中,則直接載入這個類,原來的bug類可能就被覆蓋了
看下PathClassLoader程式碼
DexClassLoader程式碼
兩個ClassLoader就兩三行程式碼,只是呼叫了父類的建構函式.
在BaseDexClassLoader 建構函式中建立一個DexPathList類的例項,這個DexPathList的建構函式會建立一個dexElements 陣列
然後BaseDexClassLoader 重寫了findClass方法,呼叫了pathList.findClass,跳到DexPathList類中.
會遍歷這個陣列,然後初始化DexFile,如果DexFile不為空那麼呼叫DexFile類的loadClassBinaryName方法返回Class例項.
歸納上面的話就是:ClassLoader會遍歷這個陣列,然後載入這個陣列中的dex檔案.
而ClassLoader在載入到正確的類之後,就不會再去載入有Bug的那個類了,我們把這個正確的類放在Dex檔案中,讓這個Dex檔案排在dexElements陣列前面即可.
CLASS_ISPREVERIFIED問題
根據QQ空間談到的在虛擬機器啟動的時候,在verify選項被開啟的時候,如果static方法、private方法、建構函式等,其中的直接引用(第一層關係)到的類都在同一個dex檔案中,那麼該類就會被打上CLASS_ISPREVERIFIED標誌,且一旦類被打上CLASS_ISPREVERIFIED標誌其他dex就不能再去替換這個類。所以一定要想辦法去阻止類被打上CLASS_ISPREVERIFIED標誌。
為了阻止類被打上CLASS_ISPREVERIFIED標誌,QQ空間開發團隊提出了一個方法是先將一個預備好的hack.dex加入到dexElements
的第一項,讓後面的dex的所有類都引用hack.dex其中的一個類,這樣原來的class1.dex、class2.dex、class3.dex中的所有類都引用了hack.dex的類,所以其中的都不會打上CLASS_ISPREVERIFIED標誌。
比如Qzon團隊的 安卓App熱補丁動態修復技術介紹 (這個一定要看!!! 他是熱修復元老級文章,也是重點抄襲物件)
動態載入class檔案,然後呼叫反射完成修復的原理:Java程式在執行的時候,JVM通過類載入機制(ClassLoader)把class檔案載入到記憶體中,只有class檔案被載入記憶體,才能被其他class引用,使程式正確執行起來.Java中的ClassLoader有三種. 1. Bootstrap ClassLoader 由C++寫的,由JVM啟動. 啟動類載入器,負責載入java基礎類,對應的檔案是%JRE_HOME/lib/ 目錄下的rt.jar、resources.jar、charsets.jar和class等 2.Extension ClassLoader Java類,繼承自URLClassLoader 擴充套件類載入器,對應的檔案是 %JRE_HOME/lib/ext 目錄下的jar和class等 3.App ClassLoader
Java類,繼承自URLClassLoader 系統類載入器,對應的檔案是應用程式classpath目錄下的所有jar和class等
這裡要注意一點:只有被同一個類載入器例項載入並且檔名相同的class檔案才被認為是同一個class. 下面來一個小例子: 因為系統的ClassLoader只會載入指定目錄下的class檔案,如果你想載入自己的class檔案,那麼就可以自定義一個ClassLoader.\ 如何自定義ClassLoader 新建一個類繼承自java.lang.ClassLoader,重寫它的findClass方法。--將class位元組碼陣列轉換為Class類的例項---呼叫loadClass方法即可
我先建一個叫Log的類,很簡單,只有一句列印
|