了解 Android 類加載器的工作原理,DexPathList 在類加載過程中的作用
類加載器
在Android中,類加載器(ClassLoader)是一個(gè)重要的組件,負(fù)責(zé)在運(yùn)行時(shí)動(dòng)態(tài)加載JVM和Android類庫。Android的類加載器系統(tǒng)基于JVM的類加載器模型,但有一些特定的調(diào)整和優(yōu)化,以適應(yīng)Android平臺(tái)的需要。
(1) Bootstrap ClassLoader:
- 這是最頂層的類加載器,由JVM實(shí)現(xiàn)。
- 主要加載Java和Android核心類庫。
- 通常通過null作為父加載器。
(2) PathClassLoader(或DexClassLoader):
- Android特有的類加載器,用于從APK文件、DEX文件或JAR/ZIP文件中加載類。
- PathClassLoader是Android應(yīng)用默認(rèn)的類加載器,用于加載應(yīng)用的類和資源。
DexClassLoader是PathClassLoader的一個(gè)子類,提供了從指定的路徑加載DEX文件的能力,動(dòng)態(tài)加載插件或模塊化場(chǎng)景常用加載器。
//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);
}
}
可以發(fā)現(xiàn)PathClassLoader和DexClassLoader源碼很簡(jiǎn)單,只包含了一個(gè)構(gòu)造函數(shù),去調(diào)用父類BaseDexClassLoader(所有的工作都應(yīng)該是在BaseDexClassLoader里完成的了)。而這兩個(gè)加載器不同的是PathClassLoader的構(gòu)造中少了optimizedDirectory這個(gè)參數(shù),原因是PathClassLoader是加載/data/app中的apk,也就是系統(tǒng)中的apk,而這部分的apk都會(huì)解壓釋放dex到指定的目錄中,這個(gè)操作由系統(tǒng)完成,不需要單獨(dú)傳入路徑,而DexClassLoader傳入,用來緩存需要加載的dex文件,并創(chuàng)建一個(gè)DexFile對(duì)象,如果為null,會(huì)直接使用dex文件原有路徑創(chuàng)建DexFile(這個(gè)參數(shù)已經(jīng)棄用,自API26起無效)。
(3) System ClassLoader(或AppClassLoader):
- Android系統(tǒng)的應(yīng)用類加載器,繼承自URLClassLoader。
- 用于加載Android系統(tǒng)的類和應(yīng)用的類。
- 在Android中不直接引用System ClassLoader或AppClassLoader,通過ClassLoader.getSystemClassLoader()獲取。
(4) 自定義ClassLoader:
- 可以繼承ClassLoader類或其子類(如DexClassLoader)來創(chuàng)建自定義的類加載器。
- 自定義類加載器可以用于加載網(wǎng)絡(luò)上的類、從數(shù)據(jù)庫加載加密的類、或者實(shí)現(xiàn)更復(fù)雜的類加載邏輯。
類加載器的主要用途:
- 動(dòng)態(tài)加載和執(zhí)行代碼,如插件化開發(fā)、熱更新等。
- 加載和執(zhí)行不同來源的代碼,如從網(wǎng)絡(luò)下載的JAR包或DEX文件。
- 隔離不同來源的代碼,防止類沖突和安全問題。
注意:濫用類加載器可能導(dǎo)致內(nèi)存泄漏和性能問題。在使用類加載器時(shí),應(yīng)該仔細(xì)考慮其生命周期和資源管理。
DexPathList
DexPathList是DexClassLoader和BaseDexClassLoader等類加載器用于處理DEX文件路徑的一個(gè)內(nèi)部類。當(dāng)使用DexClassLoader或BaseDexClassLoader加載DEX文件時(shí),DexPathList起到了關(guān)鍵的作用。
(1) 作用:DexPathList負(fù)責(zé)管理和維護(hù)DEX文件的路徑信息,使類加載器能夠正確地找到并加載DEX文件中的類。
(2) 構(gòu)造:DexPathList在DexClassLoader或BaseDexClassLoader的構(gòu)造函數(shù)中被創(chuàng)建。構(gòu)造DexPathList時(shí),需要提供DEX文件的路徑、優(yōu)化目錄、庫路徑以及父類加載器等參數(shù)。
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有一個(gè)私有的final成員變量dexElements,是一個(gè)Element數(shù)組,包含了所有DEX文件的Element對(duì)象,每個(gè)Element對(duì)象對(duì)應(yīng)一個(gè)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的構(gòu)造函數(shù)中,會(huì)調(diào)用makeDexElements()方法來加載DEX文件。這個(gè)方法會(huì)遍歷提供的DEX文件路徑列表,并為每個(gè)DEX文件創(chuàng)建一個(gè)Element對(duì)象,然后將這些Element對(duì)象添加到dexElements數(shù)組中。
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {
// 1.創(chuàng)建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版本中,處理方式有細(xì)微差別)
} else {
zip = file;
dex = loadDexFile(file, optimizedDirectory);
}
...
// 3.將dex文件或壓縮文件包裝成Element對(duì)象,并添加到Element集合中
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
// 4.將Element集合轉(zhuǎn)成Element數(shù)組返回
return elements.toArray(new Element[elements.size()]);
}
(5) 加載類:當(dāng)類加載器需要加載一個(gè)類時(shí),會(huì)通過DexPathList的loadClass()方法來實(shí)現(xiàn)。這個(gè)方法會(huì)遍歷dexElements數(shù)組中的每個(gè)Element對(duì)象,并嘗試從對(duì)應(yīng)的DEX文件中加載類。一旦找到需要加載的類,就會(huì)返回該類的Class對(duì)象。
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
// 遍歷出一個(gè)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) 優(yōu)化:為了提高性能,DexPathList還支持DEX文件的優(yōu)化。在加載DEX文件時(shí),可以將DEX文件優(yōu)化到指定的目錄中,以減少內(nèi)存占用和提高加載速度。