相關(guān)閱讀
插件化知識(shí)梳理(1) - Small 框架之如何引入應(yīng)用插件
插件化知識(shí)梳理(2) - Small 框架之如何引入公共庫插件
插件化知識(shí)梳理(3) - Small 框架之宿主分身
插件化知識(shí)梳理(4) - Small 框架之如何實(shí)現(xiàn)插件更新
插件化知識(shí)梳理(5) - Small 框架之如何不將插件打包到宿主中
插件化知識(shí)梳理(6) - Small 源碼分析之 Hook 原理
插件化知識(shí)梳理(7) - 類的動(dòng)態(tài)加載入門
插件化知識(shí)梳理(8) - 類的動(dòng)態(tài)加載源碼分析
插件化知識(shí)梳理(9) - 資源的動(dòng)態(tài)加載示例及源碼分析
插件化知識(shí)梳理(10) - Service 插件化實(shí)現(xiàn)及原理
一、前言
在 插件化知識(shí)梳理(7) - 類的動(dòng)態(tài)加載入門 中,我們通過一個(gè)例子來演示了如何通過PathClassLoader
和DexClassLoader
實(shí)現(xiàn)類的動(dòng)態(tài)加載达布。今天這篇文章,我們一起來對(duì)這個(gè)類加載內(nèi)部的實(shí)現(xiàn)源碼進(jìn)行一次簡單的走讀铐维。源碼的地址為 地址 版姑,友情提示谴供,需要翻墻能扒。
二移层、源碼解析
整個(gè)加載過程設(shè)計(jì)到的類,如下圖所示:
2.1 BaseDexClassLoader
從上面的圖中赫粥,我們可以看到DexClassLoader
和PathClassLoader
,都是BaseDexClassLoader
的子類予借,而當(dāng)我們調(diào)用以上兩個(gè)類的構(gòu)造方法時(shí)越平,其實(shí)都是調(diào)用了super()
方法,也就是BaseDexClassLoader
的構(gòu)造方法灵迫,它支持傳入一下四個(gè)參數(shù):
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());
}
}
-
dexPath
:包含了類和資源的jar/apk
文件秦叛,也就是上一篇例子當(dāng)中的plugin_dex.jar
,如果有多個(gè)文件瀑粥,那么用File.pathSeparator
進(jìn)行分割挣跋,如果我們傳入的是jar/apk
文件,那么它會(huì)先將里面的.dex
文件解壓到內(nèi)存當(dāng)中狞换,而如果是.dex
文件避咆,那么將不會(huì)有這一過程舟肉。 -
optimizedDirectory
:這個(gè)參數(shù)目前已經(jīng)被廢棄了,沒有什么作用查库。 -
librarySearchPath
:Native
庫的路徑路媚,同樣可以用File.pathSeparator
進(jìn)行分割。 -
parent
:父加載器的實(shí)例樊销。
而DexClassLoader
和PathClassLoader
的區(qū)別就在于后者不支持傳入optimizedDirectory
這個(gè)參數(shù)整慎,現(xiàn)在看來,對(duì)于最新的源碼围苫,這個(gè)參數(shù)已經(jīng)被廢棄了裤园,那么這兩個(gè)類其實(shí)是一樣的。但是具體的實(shí)現(xiàn)剂府,還是要看手機(jī)的安卓版本拧揽。
2.2 DexPathList
在前面的例子當(dāng)中,獲得PathClassLoader/DexClassLoader
實(shí)例之后周循,調(diào)用了loadClass
方法强法,它其實(shí)調(diào)用的是基類ClassLoader
中的方法:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
//先查看該類是否已經(jīng)被加載過
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//優(yōu)先調(diào)用父加載器進(jìn)行加載.
c = parent.loadClass(name, false);
} else {
//2.如果沒有父加載器,那么使用 bootstrap 進(jìn)行加載湾笛。
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
//調(diào)用 findClass 方法饮怯。
long t1 = System.nanoTime();
c = findClass(name);
}
}
return c;
}
對(duì)于BaseDexClassLoader
,最終會(huì)走到他們重寫的findClass
方法嚎研,而該方法又會(huì)去通過pathList
去尋找蓖墅,如果找不到,那么就會(huì)拋出異常临扮,
@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;
}
并且其它的公有方法论矾,都是通過pathList
去尋找的,因此這個(gè)pathList
是如何構(gòu)成的就是我們分析源碼的關(guān)鍵杆勇。
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
//dex/resource (class path) elements
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
//application native library directories
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
//system native library directories
this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
//native library path elements
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
在上面的構(gòu)造方法中贪壳,最關(guān)鍵的就是通過makeDexElements
和makePathElements
來構(gòu)建dexElements
和nativeLibraryPathElements
,它們兩個(gè)分別為Element
和NativeLibraryElement
類型的數(shù)組蚜退,在 插件化知識(shí)梳理(7) - 類的動(dòng)態(tài)加載入門 中闰靴,這兩個(gè)變量的值為:
2.3 makeDexElements
下面,我們來看一下makeDexElements
的內(nèi)部實(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;
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
if (file.isDirectory()) {
// We support directories for looking up resources. Looking up resources in
// directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
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;
}
這里面钻注,最終會(huì)創(chuàng)建一個(gè)和files
相等大小的elements
數(shù)組蚂且,其最終目的是為每個(gè)Element
中的dexFile
賦值,而dexFile
則是通過loadDexFile
方法創(chuàng)建的幅恋。
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}
這里面杏死,會(huì)根據(jù)optimizedDirectory
的區(qū)別,來調(diào)用DexFile
不同的函數(shù),我們先看靜態(tài)方法淑翼,可以看到腐巢,它里面也是調(diào)用了new DexFile
來返回一個(gè)DexFile
對(duì)象:
static DexFile loadDex(String sourcePathName, String outputPathName,
int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app
* decided to open it multiple times. In practice this may not
* be a real issue.
*/
return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
}
這里面,又會(huì)調(diào)用openDex
方法窒舟,得到一個(gè)mCookie
變量系忙,在前面的例子中,這個(gè)mCookie
是一個(gè)long
型的對(duì)象惠豺,對(duì)于里面的內(nèi)部實(shí)現(xiàn)银还,可以參見這篇文章 跟蹤源碼分析 Android DexClassLoader 加載機(jī)制
以上就是整個(gè)構(gòu)建
pathList
的過程,下面洁墙,我們來看一下前面所說的DexFileList
中findClass
的過程蛹疯。
2.4 尋找 Class 的過程
DexFileList
中尋找類的代碼如下:
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;
}
它會(huì)遍歷先前構(gòu)建的Element
數(shù)組,調(diào)用每個(gè)的findClass
方法热监,直到找到為止捺弦,而Element
中的該方法,則會(huì)調(diào)用在2.3
中創(chuàng)建的DexFile
的loadClassBinaryName
來查找該Class
對(duì)象:
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;
}
三孝扛、小結(jié)
經(jīng)過一次簡單的源碼走讀列吼,我們可以知道,DexClassLoader/PathClassLoader
的內(nèi)部苦始,為每一個(gè)傳入的jar/apk/dex
文件寞钥,都創(chuàng)建了一個(gè)Element
變量,它們被保存在DexFileList
當(dāng)中陌选,而每一個(gè)Element
變量中理郑,又包含了一個(gè)關(guān)鍵的DexFile
類,之后我們通過DexClassLoader/PathClassLoader
尋找類或者資源時(shí)咨油,其實(shí)最終都是調(diào)用了DexFile
中的Native
方法您炉,如果有興趣的同學(xué)可以去研究這些方法的內(nèi)部實(shí)現(xiàn)。
最后役电,簡單地提一下赚爵,在Small
的源碼當(dāng)中,并沒有直接使用DexClassLoader/PathClassLoader
法瑟,它首先是直接調(diào)用了DexFile
的靜態(tài)方法來為每一個(gè)插件創(chuàng)建一個(gè)DexFile
:
之后囱晴,再通過反射,將這些
DexFile
加入到宿主的ClassLoader
當(dāng)中瓢谢,而不是像我們之前那樣,為每一個(gè)插件都創(chuàng)建一個(gè)ClassLoader
驮瞧。該方法中氓扛,會(huì)像
DexFileList
中所做的那樣,通過makeDexElement
方法,為每一個(gè)DexFile
創(chuàng)建一個(gè)Element
對(duì)象:最后采郎,再將這個(gè)對(duì)象加入到
pathList
變量中:更多文章千所,歡迎訪問我的 Android 知識(shí)梳理系列:
- Android 知識(shí)梳理目錄:http://www.reibang.com/p/fd82d18994ce
- 個(gè)人主頁:http://lizejun.cn
- 個(gè)人知識(shí)總結(jié)目錄:http://lizejun.cn/categories/