了解 Android 類加載器的工作原理,DexPathList 在類加載過程中的作用
類加載器
在Android中,類加載器(ClassLoader)是一個重要的組件,負責在運行時動態加載JVM和Android類庫。Android的類加載器系統基于JVM的類加載器模型,但有一些特定的調整和優化,以適應Android平臺的需要。
(1) Bootstrap ClassLoader:
- 這是最頂層的類加載器,由JVM實現。
- 主要加載Java和Android核心類庫。
- 通常通過null作為父加載器。
(2) PathClassLoader(或DexClassLoader):
- Android特有的類加載器,用于從APK文件、DEX文件或JAR/ZIP文件中加載類。
- PathClassLoader是Android應用默認的類加載器,用于加載應用的類和資源。
DexClassLoader是PathClassLoader的一個子類,提供了從指定的路徑加載DEX文件的能力,動態加載插件或模塊化場景常用加載器。
//DexClassLoader.java
package dalvik.system;
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
//PathClassLoader
package dalvik.system;
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和DexClassLoader源碼很簡單,只包含了一個構造函數,去調用父類BaseDexClassLoader(所有的工作都應該是在BaseDexClassLoader里完成的了)。而這兩個加載器不同的是PathClassLoader的構造中少了optimizedDirectory這個參數,原因是PathClassLoader是加載/data/app中的apk,也就是系統中的apk,而這部分的apk都會解壓釋放dex到指定的目錄中,這個操作由系統完成,不需要單獨傳入路徑,而DexClassLoader傳入,用來緩存需要加載的dex文件,并創建一個DexFile對象,如果為null,會直接使用dex文件原有路徑創建DexFile(這個參數已經棄用,自API26起無效)。
(3) System ClassLoader(或AppClassLoader):
- Android系統的應用類加載器,繼承自URLClassLoader。
- 用于加載Android系統的類和應用的類。
- 在Android中不直接引用System ClassLoader或AppClassLoader,通過ClassLoader.getSystemClassLoader()獲取。
(4) 自定義ClassLoader:
- 可以繼承ClassLoader類或其子類(如DexClassLoader)來創建自定義的類加載器。
- 自定義類加載器可以用于加載網絡上的類、從數據庫加載加密的類、或者實現更復雜的類加載邏輯。
類加載器的主要用途:
- 動態加載和執行代碼,如插件化開發、熱更新等。
- 加載和執行不同來源的代碼,如從網絡下載的JAR包或DEX文件。
- 隔離不同來源的代碼,防止類沖突和安全問題。
注意:濫用類加載器可能導致內存泄漏和性能問題。在使用類加載器時,應該仔細考慮其生命周期和資源管理。
DexPathList
DexPathList是DexClassLoader和BaseDexClassLoader等類加載器用于處理DEX文件路徑的一個內部類。當使用DexClassLoader或BaseDexClassLoader加載DEX文件時,DexPathList起到了關鍵的作用。
(1) 作用:DexPathList負責管理和維護DEX文件的路徑信息,使類加載器能夠正確地找到并加載DEX文件中的類。
(2) 構造:DexPathList在DexClassLoader或BaseDexClassLoader的構造函數中被創建。構造DexPathList時,需要提供DEX文件的路徑、優化目錄、庫路徑以及父類加載器等參數。
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
}
(3) 成員變量:DexPathList有一個私有的final成員變量dexElements,是一個Element數組,包含了所有DEX文件的Element對象,每個Element對象對應一個DEX文件。
private final Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
...
this.definingContext = definingContext;
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
...
}
(4) 加載DEX文件:在DexPathList的構造函數中,會調用makeDexElements()方法來加載DEX文件。這個方法會遍歷提供的DEX文件路徑列表,并為每個DEX文件創建一個Element對象,然后將這些Element對象添加到dexElements數組中。
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {
// 1.創建Element集合
ArrayList<Element> elements = new ArrayList<Element>();
// 2.遍歷所有dex文件(也可能是jar、apk或zip文件)
for (File file : files) {
ZipFile zip = null;
DexFile dex = null;
String name = file.getName();
...
// 如果是dex文件
if (name.endsWith(DEX_SUFFIX)) {
dex = loadDexFile(file, optimizedDirectory);
// 如果是apk、jar、zip文件(這部分在不同的Android版本中,處理方式有細微差別)
} else {
zip = file;
dex = loadDexFile(file, optimizedDirectory);
}
...
// 3.將dex文件或壓縮文件包裝成Element對象,并添加到Element集合中
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
// 4.將Element集合轉成Element數組返回
return elements.toArray(new Element[elements.size()]);
}
(5) 加載類:當類加載器需要加載一個類時,會通過DexPathList的loadClass()方法來實現。這個方法會遍歷dexElements數組中的每個Element對象,并嘗試從對應的DEX文件中加載類。一旦找到需要加載的類,就會返回該類的Class對象。
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
// 遍歷出一個dex文件
DexFile dex = element.dexFile;
if (dex != null) {
// 在dex文件中查找類名與name相同的類
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
(6) 優化:為了提高性能,DexPathList還支持DEX文件的優化。在加載DEX文件時,可以將DEX文件優化到指定的目錄中,以減少內存占用和提高加載速度。