Java中的ClassLoader
ClassLoader 中與加載類相關(guān)的方法:
方法 | 說明 |
---|---|
getParent() | 返回該類加載器的父類加載器 |
loadClass(String name) | 加載名稱為 name的類贷屎,返回的結(jié)果是 java.lang.Class類的實例。 |
findClass(String name) | 查找名稱為 name的類,返回的結(jié)果是 java.lang.Class類的實例。 |
findLoadedClass(String name) | 查找名稱為 name的已經(jīng)被加載過的類,返回的結(jié)果是 java.lang.Class類的實例乡括。 |
defineClass(String name, byte[] b, int off, int len) | 把字節(jié)數(shù)組 b中的內(nèi)容轉(zhuǎn)換成 Java 類,返回的結(jié)果是 java.lang.Class類的實例智厌。這個方法被聲明為 final的诲泌。 |
resolveClass(Class<?> c) | 鏈接指定的 Java 類。 |
類加載器的樹狀組織結(jié)構(gòu)
Java 中的類加載器大致可以分成兩類铣鹏,一類是系統(tǒng)提供的敷扫,另外一類則是由 Java 應(yīng)用開發(fā)人員編寫的。系統(tǒng)提供的類加載器主要有下面三個:
- 引導(dǎo)類加載器(bootstrap class loader):它用來加載 Java 的核心庫诚卸,是用原生代碼來實現(xiàn)的葵第,并不繼承自 java.lang.ClassLoader绘迁。
- 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現(xiàn)會提供一個擴展庫目錄卒密。該類加載器在此目錄里面查找并加載 Java 類缀台。
- 系統(tǒng)類加載器(system class loader):也稱為應(yīng)用類加載器,它的父加載器為擴展類加載器哮奇。它從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中加載類膛腐,它是用戶自定義的類加載器的默認(rèn)父加載器。系統(tǒng)類加載器是純Java類屏镊,是java.lang.ClassLoader類的子類依疼。
父子加載器并非繼承關(guān)系,也就是說子加載器不一定是繼承了父加載器而芥。
雙親委托模式
通俗的講,就是某個特定的類加載器在接到加載類的請求時膀值,首先將加載任務(wù)委托給父類加載器棍丐,依次遞歸,如果父類加載器可以完成類加載任務(wù)沧踏,就成功返回歌逢;只有父類加載器無法完成此加載任務(wù)時,才自己去加載翘狱。
- 因為這樣可以避免重復(fù)加載秘案,當(dāng)父親已經(jīng)加載了該類的時候,就沒有必要子ClassLoader再加載一次潦匈。
- 考慮到安全因素阱高,我們試想一下,如果不使用這種委托模式茬缩,那我們就可以隨時使用自定義的String來動態(tài)替代java核心api中定義類型赤惊,這樣會存在非常大的安全隱患,而雙親委托的方式凰锡,就可以避免這種情況未舟,因為String已經(jīng)在啟動時被加載,所以用戶自定義類是無法加載一個自定義的ClassLoader掂为。
Android中的ClassLoader
JVM中ClassLoader通過defineClass方法加載jar里面的Class裕膀,而Android中這個方法被棄用了。
@Deprecated
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError {
throw new UnsupportedOperationException("can't load this type of class file");
}
取而代之的是loadClass方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
}
}
return c;
}
DexClassLoader與PathClassLoader
/**
* dexPath:被解壓的apk路徑勇哗,不能為空昼扛。
optimizedDirectory:解壓后的.dex文件的存儲路徑,不能為空智绸。這個路徑強烈建議使用應(yīng)用程序的私有路徑野揪,不要放到sdcard上访忿,否則代碼容易被注入攻擊。
libraryPath:os庫的存放路徑斯稳,可以為空海铆,若有os庫,必須填寫挣惰。
parent:父親加載器卧斟,一般為context.getClassLoader(),使用當(dāng)前上下文的類加載器。
*/
class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
這兩者只是簡單的對BaseDexClassLoader做了一下封裝憎茂,具體的實現(xiàn)還是在父類里珍语。
但是兩者還是有區(qū)別的,PathClassLoader的optimizedDirectory只能是null
optimizedDirectory是用來緩存我們需要加載的dex文件的竖幔,并創(chuàng)建一個DexFile對象板乙,如果它為null,那么會直接使用dex文件原有的路徑來創(chuàng)建DexFile對象拳氢。
DexClassLoader可以指定自己的optimizedDirectory募逞,所以它可以加載外部的dex,因為這個dex會被復(fù)制到內(nèi)部路徑的optimizedDirectory馋评;而PathClassLoader沒有optimizedDirectory放接,所以它只能加載內(nèi)部的dex,這些大都是存在系統(tǒng)中已經(jīng)安裝過的apk里面的留特。
- DexClassLoader:可以加載jar/apk/dex纠脾,可以從SD卡中加載未安裝的apk;
- PathClassLoader:要傳入系統(tǒng)中apk的存放Path蜕青,所以只能加載已經(jīng)安裝的apk文件苟蹈;
參考鏈接:
深入探討 Java 類加載器
Android插件化學(xué)習(xí)之路(二)之ClassLoader完全解析
加載類的過程
java.lang.Object
? java.lang.ClassLoader
? dalvik.system.BaseDexClassLoader
? dalvik.system.DexClassLoader / dalvik.system.PathClassLoader
- Android中,ClassLoader用loadClass方法來加載我們需要的類
- loadClass方法調(diào)用了findClass方法市咆,而BaseDexClassLoader重載了這個方法
- 結(jié)果還是調(diào)用了DexPathList的findClass
- 最后調(diào)用了Native方法defineClass加載類
調(diào)用.dex中的代碼
Java程序中汉操,JVM虛擬機是通過類加載器ClassLoader加載.jar文件里面的類的。Android也類似蒙兰,不過android用的是Dalvik/ART虛擬機磷瘤,不是JVM,也不能直接加載.jar文件搜变,而是加載dex文件采缚。
先要通過Android SDK提供的DX工具把.jar文件優(yōu)化成.dex文件,然后Android的虛擬機才能加載挠他。注意扳抽,有的Android應(yīng)用能直接加載.jar文件,那是因為這個.jar文件已經(jīng)經(jīng)過優(yōu)化,只不過后綴名沒改(其實已經(jīng)是.dex文件)贸呢。
調(diào)用普通的邏輯代碼可以通過下面兩種方式:
- 使用DexClassLoader加載進來的類镰烧,我們本地并沒有這些類的源碼,所以無法直接調(diào)用楞陷,不過可以通過反射的方法調(diào)用怔鳖。
- 畢竟.dex文件也是我們自己維護的,所以可以把方法抽象成公共接口固蛾,把這些接口也復(fù)制到主項目里面去结执,就可以通過這些接口調(diào)用動態(tài)加載得到的實例的方法了。
參考鏈接:
Android動態(tài)加載dex技術(shù)初探
Android插件化學(xué)習(xí)之路(三)之調(diào)用外部.dex文件中的代碼