八、Android 熱修復瞭解
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 專用的類載入器。
那麼這兩個類載入器有何區別呢。
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陣列的最後一個元素中,所以沒有機會被拿到而已)。