Android ClassLoader解析(2) - Android ClassLoader
ClassLoader的主要作用就是載入類,且Android應用邏輯也是使用Java語言編寫的,但Android ClassLoader和Java ClassLoader的原理卻不太一樣,關於Java 中ClassLoader的主要原理及使用見上一篇博文,本文主要比較兩者的差別後,主要討論Android ClassLoader的原理及使用。
Android ClassLoader & Java ClassLoader
Java中的ClassLoader可以載入jar檔案和Class檔案;但在Android中不同,Android打包後是apk應用,解壓後看到其中包含一個class.dex檔案,無論DVM還是ART他們載入的不再是Class檔案,而是Dex檔案,因此ClassLoader在載入類的過程中也略有不同。
ClassLoader的繼承關係

其中,他們的關係如下:
- ClassLoader:抽象類,其中定義了ClassLoader的主要功能。BootClassLoader是它的內部類;
- SecureClassLoader: 和JDK8中的SecureClassLoader類的程式碼是一樣的,它繼承了抽象類ClassLoader。SecureClassLoader並不是ClassLoader的實現類,而是拓展了ClassLoader類加入了許可權方面的功能,加強了ClassLoader的安全性;
- URLClassLoader:和JDK8中的URLClassLoader類的程式碼是一樣的,它繼承自SecureClassLoader,用來通過URl路徑從jar檔案和資料夾中載入類和資源;但由於dalvik不能直接識別jar,所以在Android中無法使用這個載入器;
- BaseDexClassLoader:繼承自ClassLoader,是抽象類ClassLoader的具體實現類,PathClassLoader和DexClassLoader都繼承它,他們的主要邏輯都在這裡;
- InMemoryDexClassLoader:是Android8.0新增的類載入器,繼承自BaseDexClassLoader,用於載入記憶體中的dex檔案;
BootClassLoader
該類在 libcore/ojluni/src/main/java/java/lang/ClassLoader.java
,與Java中的BootstrapClassLoader不同,他不是C/C++程式碼,而是Java程式碼,且是ClassLoader的內部類,且BootClassLoader是在Zygote程序的入口方法中建立的。(注:BootClassLoader的訪問修飾符是預設的,只有在同一個包中才可以訪問,因此我們無法使用)
PathClassLoader
該類在 libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
,用來載入系統類和應用程式的類,且不建議開發直接使用。程式碼如下:
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); } }
PathClassLoader則是在Zygote程序建立SystemServer程序時建立的,其構造方法有三個引數:
- dexPath: 目標類所在的APK或jar檔案的路徑,多個路徑用檔案分隔符分隔,預設檔案分隔符為‘:’。支援載入APK、DEX和JAR,也可以從SD卡進行載入,最後都會生成一個對應的dex檔案。且最終將dexPath路徑上的檔案ODEX優化到內部位置optimizedDirectory,再進行載入;
- librarySearchPath: 包含 C/C++ 庫的路徑集合,多個路徑用檔案分隔符分隔分割,可以為null;
- parent: ClassLoader的parent;
DexClassLoader
該類在 libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
,可以載入dex檔案以及包含dex的apk檔案或jar檔案,也支援從SD卡進行載入。因此可以在應用未安裝的情況下載入dex相關檔案。因此,它是熱修復和外掛化技術的基礎。程式碼如下:
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), librarySearchPath, parent); } }
其中,引數optimizedDirectory表示儲存ODEX檔案的路徑,ClassLoader只能載入內部儲存路徑中的dex檔案,所以這個路徑必須是一個內部儲存路徑。 應用程式第一次被載入的時候,為了提高以後的啟動速度和執行效率,Android系統會對dex相關檔案做一定程度的優化,並生成一個ODEX檔案,此後再執行這個應用程式的時候,只要載入優化過的ODEX檔案就行了,省去了每次都要優化的時間。
PathClassLoader沒有引數optimizedDirectory,這是因為PathClassLoader已經默認了引數optimizedDirectory的路徑為: /data/dalvik-cache
ClassLoader類載入過程
閱讀原始碼可知,Android中ClassLoader載入類的過程也符合雙親委派機制。根據之前的分析可知,在查到物件的過程中,如果一直委託到頂層的父載入器依然找不到,則會呼叫findClass向下查詢:
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) { 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. c = findClass(name); } } return c; }
且PathClassLoader和DexClassLoader的實現中只有建構函式,其具體實現都在BaseDexClassLoader中,其部分原始碼為:
private final DexPathList pathList; @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = pathList.findClass(name); if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; }
DexPathList 中的 findClass:
public Class findClass(String name) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext); if (clazz != null) { return clazz; } } } return null; }
DexFile 中的loadClassBinaryName:
public Class loadClassBinaryName(String name, ClassLoader loader) { return defineClass(name, loader, mCookie); } private native static Class defineClass(String name, ClassLoader loader, int cookie);
因此,可得BaseDexClassLoader中有個pathList物件,pathList中包含一個DexFile的陣列dexElements,類載入的過程即:遍歷這個集合,通過DexFile去尋找,最終呼叫native方法的defineClass。
ClassLoader例項有多個
一個執行的Android應用至少有2個ClassLoader
- BootClassLoader:在Android系統啟動的時候建立,用於載入系統Framework層級需要的類,而我們的Android應用也有可能需要用到一些系統的類,所以APP啟動的時候也會將BootClassLoader傳進來;
- 自己的ClassLoader例項:應用啟動時建立,用於載入應用dex檔案中的類;
Android ClassLoader動態載入可能遇到的問題
- 動態載入四大元件,需要提前在Manifest中註冊;
- Resource資源問題,常見的是資源id問題;