Android 動態加載dex
首先如果僅僅是因為64K method的問題可以直接看這里 DexGuard、Proguard、Multi-dex 給出的解決方案
本文主要討論從編譯層面,dex動態加載器選擇層面以及安全層面討論dex動態加載
</div>前言
比較兩個類是否相等: 是基于采用同樣的加載器加載的,否則必不相等。
一般加載器類別
虛擬機的角度
1. 啟動類加載器(Bootstrap ClassLoader)
使用C++語言實現,虛擬機自身的一部分
2. 其他的類加載器
使用Java語言實現,獨立于JVM外部,全部繼承自類java.lang.ClassLoader
開發人員角度
以下三種都是系統提供的供開發人員使用的加載器
</div>1. 啟動類加載器(Bootstrap ClassLoader)
負責加載: JAVA_HOME\lib目錄中 能被JVM是被的類庫到JVM內存中(名稱不符合的類庫不會被加載)
無法被Java程序直接引用。
2. 擴展類加載器(Extersion ClassLoader)
負責加載: JAVA_HOME\lib\ext目錄中的類庫
可以被開發者直接使用
3. 應用程序類加載器(Application ClassLoader)
也稱為 系統類加載器
</div>負責加載: 用戶類路徑(Classpath)上所指定的類庫
可以被開發者直接使用
一般是應用程序默認的類加載器
什么是雙親委派模型?
一個類收到了類加載請求,會將請求先委派給父類加載,每層皆如此,因此所有的類加載是從上而下的,只有上層無法加載了才到下層加載。
也可以參考ClassLoader中給出的解釋:
Loads the class with the specified name, optionally linking it after loading. The following steps are performed:
- Call findLoadedClass(java.lang.String) to determine if the requested class has already been loaded.
- If the class has not yet been loaded: Invoke this method on the parent class loader.
- If the class has still not been loaded: Call findClass(java.lang.String) to find the class.</pre>
為什么要遵循雙親委派模型?
為了保證所加載的類的唯一性,保證相同的類只會被一個加載器所加載。
Dalvik虛擬機的類加載器與其他Java虛擬機的不同?
一般的Java虛擬機,是自定義繼承自ClassLoader的類加載器,然后通過defineClass方法從二進制流中加載Class,或者從Class文件中讀取。而Dalvik虛擬機是閹割以及修改過的,無法從二進制流中加載,Dalvik只識別dex文件,因此我們能加載的只是dex文件或包含dex文件的.jar或.apk。
I. Android 動態加載Dex的方式
![]()
DexFile
Android中的這幾種類加載器實際是依賴DexFile的,對于DexFile有以下兩點
- 打開的DEX文件不會直接存儲在DexFile對象中,而是存儲在對于虛擬機只讀的memory-mapped上。
- 我們無法直接調用DexFile.loadClass進行對dex的加載,只能通過ClassLoader進行加載
PathClassLoader的使用案例推薦參考: secondary-dex-gradle/…/secondarydex/plugin/
DexClassLoader的使用案例推薦參考: Custom Class Loading in Dalvik ,如果你有網絡下載dex動態打補丁的需求的話
II. 何在編譯層面實現打指定獨立dex
Ant
可以參考 這里 后面的Build Process.
Gradle
在編譯層面將指定的module拆分出來打包成dex放入assets中,完全可以參考這個方案:
secondary-dex-gradle/app/build.gradle如果不理解的可以看我fork的,我添加了中文注解: Jacksgong/secondary-dex-gradle/app/build.gradle
III. 安全性討論
動態加載Dex的安全性主要存在兩方面
- 存儲dex的文件暴露在其他應用可讀寫的目錄下
- 加載外部dex的時候沒有做好完整的安全性校驗
解決方案
- 盡量將dex放到當前應用的私有目錄下,保證只有當前應用uid可以寫甚至讀(一般就只有Context.getFileDir()/Context.getDir(String, MODE_PRIVATE)/Context.getCacheDir()),這方面目錄相關知識可以參看 Android中盡量不用Storage Permission
- 對從服務端下載或者外部加載的dex,做校驗(對文件進行哈希值校驗等)
- 將dex文件加密,通過JNI將解密代碼寫在Native層,解密之后通過defineClass指定路徑加載完成后,刪除解密后文件