提起熱修復(fù)以及插件化,相信大家肯定不陌生,而無(wú)論是熱修復(fù)還是插件化,其理論依據(jù)就是Android 類(lèi)加載機(jī)制。今天我們從源碼的角度一起學(xué)習(xí)下。
簡(jiǎn)單來(lái)講,Android中的ClassLoader主要分為BootClassLoader、PathClassLoader和DexClassLoader這三種類(lèi)型。BootClassLoader:主要負(fù)責(zé)加載Android FrameWork層中的字節(jié)碼文件; PathClassLoader:負(fù)責(zé)加載已經(jīng)安裝到系統(tǒng)APK文件中的字節(jié)碼文件;DexClassLoader:負(fù)責(zé)加載指定目錄中的字節(jié)碼文件;我們先來(lái)看下其源碼實(shí)現(xiàn):
#BootClassLoader
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
public BootClassLoader() {
super(null);
}
...
}
由上述代碼可以看出,BootClassLoader 繼承自ClassLoader抽象類(lèi),實(shí)現(xiàn)方式為單例模式,需要注意的是BootClassLoader的訪(fǎng)問(wèn)修飾符是默認(rèn)的,只有在同一個(gè)包中才可以訪(fǎng)問(wèn),所以我們?cè)趹?yīng)用程序中是無(wú)法直接調(diào)用到的。
#PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
}
}
PathClassLoader繼承自BaseDexClassLoader ,由上述代碼,很顯然,PathClassLoader中的方法實(shí)現(xiàn)都在其父類(lèi)BaseDexClassLoader 中,在這里我們分析下PathClassLoader構(gòu)造方法中各個(gè)參數(shù)的含義:
dexPath:dex文件以及包含dex的apk文件或jar文件的路徑集合,多個(gè)路徑用文件分隔符分隔,默認(rèn)文件分隔符為‘:’。
librarySearchPath:所使用到的C/C++庫(kù)存放的路徑
parent:該ClassLoader所對(duì)應(yīng)的父ClassLoader
#DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
}
}
同樣,DexClassLoader 也是繼承自BaseDexClassLoader ,相比較PathClassLoader而言,DexClassLoader的構(gòu)造方法中多了一個(gè)參數(shù)optimizedDirectory,我們看下這個(gè)參數(shù)的含義:
optimizedDirectory:Android系統(tǒng)將dex文件進(jìn)行優(yōu)化后所生成的ODEX文件的存放路徑,該路徑必須是一個(gè)內(nèi)部存儲(chǔ)路徑。PathClassLoader中使用默認(rèn)路徑“/data/dalvik-cache”,而DexClassLoader則需要我們指定ODEX優(yōu)化文件的存放路徑。
和Java中的ClassLoader類(lèi)似,Android中的ClassLoader同樣遵循雙親委托機(jī)制。上述三種ClassLoader中,PathClassLoader的parent為BootClassLoader,DexClassLoader的parent同樣為BootClassLoader,下面我們來(lái)驗(yàn)證下:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader classLoader = getClassLoader();
if (classLoader != null){
Log.e("MainActivity", classLoader.toString());
while (classLoader.getParent() != null){
classLoader = classLoader.getParent();
Log.e("MainActivity", classLoader.toString());
}
}
Log.e("MainActivity", "------------------");
TextView mText = findViewById(R.id.tv_text);
Log.e("MainActivity-TextView", mText.getClass().getClassLoader().toString());
}
}
輸出日志為:
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.administrator.mdtest-2/base.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_dependencies_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_0_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_1_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_2_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_3_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_4_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_5_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_6_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_7_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_8_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity: java.lang.BootClassLoader@245c18f6
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity: ------------------
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity-TextView: java.lang.BootClassLoader@245c18f6
由上述輸出日志,我們不僅可以驗(yàn)證,PathClassLoader的parent為BootClassLoader,同時(shí)還驗(yàn)證了我們文章開(kāi)始所說(shuō)的應(yīng)用程序的ClassLoader為PathClassLoader,F(xiàn)rameWork層的ClassLoader為BootClassLoader。
照例我們打開(kāi)源碼,看下BootClassLoader是在哪里作為parent參與構(gòu)建PathClassLoader對(duì)象的:
#ClassLoader
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
// TODO Make this a java.net.URLClassLoader once we have those?
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
沒(méi)錯(cuò),正是在ClassLoader類(lèi)中的createSystemClassLoader方法中。
好了,我們既然知道Android的ClassLoader遵循雙親委托機(jī)制,那么肯定要看下ClassLoader類(lèi)中的loadClass方法了:
#ClassLoader
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;
}
關(guān)于雙親委托機(jī)制,相信大家都了解,在這里我就不詳細(xì)介紹了,需要注意的是,在Java中,類(lèi)加載是通過(guò)defineClass方法,而在Android中,類(lèi)加載則是通過(guò)findClass方法,我們跟進(jìn)去findClass方法看下類(lèi)加載的過(guò)程(由于BaseDexClassLoader對(duì)findClass方法進(jìn)行了重寫(xiě),所以我們需要跟進(jìn)到BaseDexClassLoader類(lèi)中,而Android Studio中無(wú)法查看到BaseDexClassLoader的具體源碼,所以筆者在這里通過(guò)源碼在線(xiàn)查看網(wǎng)站:https://www.androidos.net.cn/sourcecode):
#BaseDexClassLoader
@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;
}
可以看到,在BaseDexClassLoader的findClass方法中直接調(diào)用到pathList的findClass方法進(jìn)行類(lèi)加載操作,pathList是個(gè)什么東東呢?我們看下它的定義:
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
if (reporter != null) {
reporter.report(this.pathList.getDexPaths());
}
}
由上述代碼可以看到,pathList被定義為final類(lèi)型,其對(duì)象是在BaseDexClassLoader的構(gòu)造方法中創(chuàng)建的,也就是說(shuō)在PathClassLoader對(duì)象創(chuàng)建的時(shí)候就創(chuàng)建了DexPathList對(duì)象,并將相應(yīng)參數(shù)傳入。我們跟進(jìn)去看下DexPathList的構(gòu)造方法:
//定義所要加載文件后綴
private static final String DEX_SUFFIX = ".dex";
//構(gòu)造方法中傳入的ClassLoader
private final ClassLoader definingContext;
//Element為 DexPathList 中的內(nèi)部類(lèi),其主要的成員變量為 dexFile
//DexFile:dex文件在安卓虛擬機(jī)中的具體實(shí)現(xiàn)
private Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
異常判斷操作...
//接收classloader對(duì)象
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
//重點(diǎn),通過(guò) makeDexElements 方法初始化 dexElements數(shù)組
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
...
}
我們跟進(jìn)去看下makeDexElements方法的實(shí)現(xiàn):
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
/*
* 遍歷所有的dex文件
*/
for (File file : files) {
if (file.isDirectory()) { //判斷file是否為文件夾
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) { //判斷file是否為文件
//獲取文件名稱(chēng)
String name = file.getName();
//判斷文件名稱(chēng)是否以“.dex”結(jié)尾
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
//將dex文件轉(zhuǎn)換為DexFile對(duì)象
DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
//創(chuàng)建Element對(duì)象,將DexFile對(duì)象作為參數(shù)傳入,
//并將該Element對(duì)象添加到elements數(shù)組中
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
DexFile dex = null;
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
由上述代碼,我們可以知道m(xù)akeDexElements方法的主要作用為:遍歷指定路徑下的所有文件,將其中的.dex文件轉(zhuǎn)換成DexFile對(duì)象,最終存儲(chǔ)到elements數(shù)組中。
由上述分析,我們知道類(lèi)加載操作最終是由pathList的findClass方法來(lái)實(shí)現(xiàn)的,我們繼續(xù)跟進(jìn)去pathList的findClass方法看下:
#DexPathList
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
可以看到, DexPathList 的 findClass方法中簡(jiǎn)單粗暴,對(duì)dexElements數(shù)組進(jìn)行遍歷,調(diào)用element的findClass方法來(lái)尋找當(dāng)前需要的class字節(jié)碼,簡(jiǎn)單來(lái)講就是Android在進(jìn)行類(lèi)加載的時(shí)候,會(huì)遍歷我們的每一個(gè)dex文件,來(lái)尋找所需的Class。
我們接著跟進(jìn)去element的findClass方法去看下:
#Element
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
可以看到,最終是調(diào)用到 DexFile 的loadClassBinaryName方法,我們接著跟:
#DexFile
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
...
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
...
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
可以看到最終是通過(guò)DexFile類(lèi)中的defineClassNative方法來(lái)完成所需Class的查找。
好了,Android ClassLoader源碼解析到這里就結(jié)束了,歡迎大家一起交流。