0. 如何動態修復 bug
- 1、下發補丁(內含修復好的 class)到用戶手機,即讓 app 從服務器上下載。(網絡傳輸)
- 2、app 通過某種方式,使補丁中的 class 被 app 調用(本地更新)
這里的某種方式,特指 Android 的類加載器,通過類加載器加載這些修復好的 class,覆蓋有問題的 class,理論上就能修復 bug 了。
1. PathClassLoader 與 DexClassLoader 的區別
由上述內容可知,Android 的類加載器是關鍵。
我們知道 jvm 有 ClassLoader,但是 Android 對 jvm 優化過,使用的是 dalvik/ART,且 class 文件會被打包進一個 dex 文件中,底層虛擬機有所不同,因此它們的類加載器肯定也會有所區別。
在 Android 中,要加載 dex 文件中的 class 文件就需要用到 PathClassLoader 或 DexClassLoader 這兩個 Android 專用的類加載器。
那么這兩個類加載器有何區別呢。
首先源碼查看
PathClassLoader
DexClassLoader
1.1 使用場景
- PathClassLoader:在應用啟動時創建,從 data/app/... 安裝目錄下加載 apk 文件。是 Android 默認使用的類加載器。
- DexClassLoader:可以加載任意目錄下的dex/jar/apk/zip文件,比 PathClassLoader 更靈活,是實現熱修復的基礎。
1.2 代碼差異
PathClassLoader:源碼中就 2 個構造函數,如下所示
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
- dexPath: 包含 dex 的 jar 文件或 apk 文件的路徑集,多個以文件分隔符分隔,默認是 :
- libraryPath: 包含 C/C++ 庫的路徑集,多個同樣以文件分隔符分隔,可以為空
DexClassLoader:源碼中就 1 個構造函數,如下所示
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
- dexPath: 包含 class.dex 的 apk、jar 文件路徑 ,多個用文件分隔符 (默認是 : ) 分隔
- optimizedDirectory: 用來緩存優化的 dex 文件的路徑,即從 apk 或 jar文件中提取出來的 dex 文件。該路徑不能為空,且應該是應用私有的,有讀寫權限的路徑(實際上也可以使用外部存儲空間,但是這樣的話就存在代碼注入的風險)
- libraryPath: 存儲 C/C++ 庫文件的路徑集
- parent: 父類加載器,遵從雙親委托模型
結論:
- DexClassLoader: 可以加載任意目錄下的 dex/jar/apk/zip 文件,需要指定一個 optimizedDirectory。
- PathClassLoader: 只能加載已經安裝到 Android 系統中的 apk 文件。
- 同時,它們都繼承自 BaseDexClassLoader,所以真正的實現都在 BaseDexClassLoader 內。
1.3 BaseDexClassLoader
1、當傳入一個完整的類名,調用 BaseDexClassLoader 的 findClass(String name) 方法。
2、BaseDexClassLoader 的 findClass(String name) 方法 會交給 DexPathList 的 findClass(String name, List< Throwable > suppressed) 方法處理。
3、在 DexPathList 方法的內部,會遍歷 dexElements 數組,得到具體的 Element,再通過 Element.dexFile,得到具體的 DexFile,通過 DexFile.loadClassBinaryName(name, definingContext, suppressed) 來完成類的加載
2. 熱修復實現原理
通過上述分析,我們知道,安卓的類加載器在加載一個類時會先從 BaseDexClassLoader 的 DexPathList 對象中的 Element 數組中獲取到對應的 DexFile,然后通過 DexFile 的 loadClassBinaryName 將類加載出來。
這里是通過 數組遍歷,遍歷出一個個 dex 文件。
所以,我們只要將修復好的 class 打包成一個 dex 文件,然后將它放在 Element 數組的第一個元素。這樣當類加載時,就能保證獲取到的 class 是最新修復好的 class 了。(當然,有bug的class也是存在的,不過是放在了Element數組的最后一個元素中,所以沒有機會被拿到而已)。
參考
https://juejin.im/post/5a0ad2b551882531ba1077a2#heading-13
https://jaeger.itscoder.com/android/2016/09/20/nuva-source-code-analysis.html