前言
- ClassLoader類加載,是動態(tài)加載機制及現(xiàn)在火熱的插件化機制中很基礎(chǔ)但同時又很重要的知識點,通過學(xué)習(xí)這一章節(jié),能在腦海中浮現(xiàn)出Android系統(tǒng)、應(yīng)用的加載原理整個框架。
什么是ClassLoader
- 翻譯過來就是類加載器,見名知義。類在生成字節(jié)碼.class文件后,從物理文件地址加載到Dalvik/ART虛擬機中使用。
Android中的類體現(xiàn)
- 這里會提到Android中使用較多的兩種ClassLoader,當(dāng)然還有屬于JVM的ClassLoader,有興趣的可以自行查閱。
- BootClassLoader: Dalvik/ART虛擬機用于加載Android系統(tǒng)類的Loader,應(yīng)用層通過獲取父ClassLoader的最終項。
- PathClassLoader: 我們知道,打包APK后實際上是把java文件都生成dex文件,而這個Loader就是在應(yīng)用啟動時,加載已安裝APK的dex文件。
- DexClassLoader: 常見的動態(tài)加載機制都用這個類,傳入指定路徑加載指定dex文件。
加載原理
-
ClassLoader使用的是雙親委托機制。雙親委派模型,旨在于讓頂級父類加載器先加載類,若不成功,則一層層往下加載,最終到當(dāng)前加載器。這樣做的目的是保持類加載系統(tǒng)的穩(wěn)定性,不會出現(xiàn)不同加載器加載同一個類時,出現(xiàn)多個類實例。
Android-ClassLoader加載機制.png
類加載源碼解析
- 關(guān)注ClassLoader的loadClass方法
-
BootClassLoader用于加載系統(tǒng)層的類,比較特殊,loadClass查詢虛擬機中沒有緩存時最終會調(diào)用到findClass ——> Native 方法 Class.classForName:
BootClassLoader-findClass.png 其余的loadClass都是調(diào)用到下圖中,先查找當(dāng)前l(fā)oader是否有加載到該類的緩存,若沒有,就獲取父加載器查找,遞歸直到父加載器不存在。
因此總體來看這是一個先向上委托父加載器,再向下查找的過程。若查詢?yōu)閚ull,將從當(dāng)前加載器加載類,最終會調(diào)用loadClass方法,加載成功就跳出遞歸。

-
DexClassLoader和PathClassLoader 都是繼承于BaseDexClassLoader,從構(gòu)造方法可知,optimizedDirectory這個參數(shù)已經(jīng)無效了,因為已經(jīng)沒有向下傳遞的,我們一起直接看看它的findClass方法。
BaseDexClassLoader.png

-
實際上調(diào)用的是DexPathList.findClass方法,這里有個dexElements,看看他是如何構(gòu)造出來的。
DexPathList-findClass.png -
根據(jù)我們創(chuàng)建ClassLoader時,傳入的dexPath,支持APK、DEX、JAR文件全路徑,以/分隔符分開。分別調(diào)用loadDexFile,這就是我們生成最終可用的dex文件的過程。最終構(gòu)造出dexElements。
DexPathList-makePathElements.png

-
最終調(diào)用loadClassBinaryName,這個方法最終會調(diào)到底層Native 方法DextFile.defineClassNative。
DexFile-loadClassBinaryName.png

- 根據(jù)以上的加載原理,已經(jīng)能大致了解Android類加載的過程。就是我們使用DexClassLoader或PathClassLoader加載類時,系統(tǒng)究竟做了什么,也是能有個印象,為我們接下來更加深入到類加載器在虛擬機中是如何運行的知識點建立一個基礎(chǔ)。
使用方式
-
DexClassLoader 構(gòu)造方法參數(shù)說明
DexClassLoader.png
| 參數(shù)名 | 描述 |
|---|---|
| dexPath | 待提取dex的文件全路徑,多個時以 ":" 分隔符(在Android中以File.pathSeparator定義)隔開 |
| optimizedDirectory | 提取到dex文件后存放文件夾路徑,但現(xiàn)在已經(jīng)無效了 |
| libraryPath | so文件所在文件夾路徑, 多個時以 ":" 分隔符(在Android中以File.pathSeparator定義)隔開 |
| parent | 父classloader |
舉個例子吧
-
本地項目新建一個系統(tǒng)已實現(xiàn)類 —— TextUtils.java,其中的isEmpty的方法體實現(xiàn)變更,項目中能否調(diào)用到我更改后的方法邏輯呢?
TextUtils.png

答案顯而易見是不可以的,前面我們學(xué)到,類加載委托是從下至上,類查找是從上至下的,頂級類加載器父類BootClassloader查找到TextUtils時,會首先把類加載入緩存。級別更低的ClassLoader均從緩存獲取成功,因此項目中使用PathClassloader才能加載到的TextUtils是無法成功的。

- 用DexClassLoader的方式加載外部dex文件,首先新建類Dextest用于打包并使用dx工具生成dextest.jar放在外部擴展文件夾dx/下。接著構(gòu)建DexClassLoader并反射觸發(fā)即可成功。



總結(jié)
- 我們?nèi)粝肓私飧鞣N加載流程,還是需要多深入源碼,Android-ClassLoader實現(xiàn)邏輯算是非常清晰易懂,但對我們?nèi)粘i_發(fā)如插件化方案會有非常大的幫助。
- 閱讀源碼由于各模塊代碼體量都是不小的,為了效率應(yīng)該挑重點看,也可以結(jié)合博客加快自己看源碼的進(jìn)程。
native底層代碼閱讀:







