- 本篇是基于上一篇ClassLoader(一) —— Java ClassLoader。
- Android虛擬機(jī)和JVM一樣,運(yùn)行程序時(shí)首先要將對(duì)應(yīng)的類加載到內(nèi)存中。但是和JVM不同的是Android虛擬機(jī)上運(yùn)行的是Dex字節(jié)碼,因此Android的ClassLoader和Java的ClassLoader有一定不同。
Android 類加載
-
Android中的類加載器有
- BootClassLoader
- URLClassLoader
- PathClassLoader
- DexClassLoader
- BaseDexClassLoader
- ClassLoader
其中BootClassLoader,PathClassLoader和DexClassLoader是重點(diǎn)。
看看他們之間的繼承關(guān)系:
Android ClassLoader 繼承.PNG
BootClassLoader
- BootClassLoader在Android系統(tǒng)啟動(dòng)的時(shí)候就被創(chuàng)建,它用于加載一些Android系統(tǒng)框架的類,包括APP用到的一些系統(tǒng)類。它是ClassLoader中的內(nèi)部類,由Java實(shí)現(xiàn)。這個(gè)內(nèi)部類是包內(nèi)可見(jiàn),所以我們沒(méi)法使用。
URLClassLoader
- 它繼承自SecureClassLoader,用來(lái)通過(guò)URl路徑從jar文件和文件夾中加載類和資源。由于 dalvik 不能直接識(shí)別jar,所以在 Android 中無(wú)法使用這個(gè)加載器。
PathClassLoader
PathClassLoader是用來(lái)加載Android系統(tǒng)類和應(yīng)用的類。
在Dalvik虛擬機(jī)上PathClassLoader只能加載已安裝的apk的dex文件。但在ART虛擬機(jī)上可以加載未安裝的apk的dex文件。
-
PathClassLoader的源碼,只有2個(gè)構(gòu)造方法:
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super(dexPath, null, libraryPath, parent); } }由于都是只調(diào)用了父類BaseDexClassLoader的構(gòu)造方法,所以每個(gè)參數(shù)的含義將會(huì)留到BaseDexClassLoader再分析。
DexClassLoader
DexClassLoader可以加載一個(gè)未安裝的APK,也可以加載其它包含dex文件的JAR/ZIP類型的文件,可以從 SD 卡上加載包含 class.dex 的 .jar 和 .apk 文件,這也是插件化和熱修復(fù)的基礎(chǔ),在不需要安裝應(yīng)用的情況下,完成需要使用的 dex 的加載。
上面說(shuō)dalvik不能直接識(shí)別jar,DexClassLoader卻可以加載jar文件,這難道不矛盾嗎?其實(shí)在BaseDexClassLoader里對(duì)".jar",".zip",".apk",".dex"后綴的文件最后都會(huì)生成一個(gè)對(duì)應(yīng)的dex文件,所以最終處理的還是dex文件,而URLClassLoader并沒(méi)有做類似的處理。
-
DexClassLoader的源碼,只有1個(gè)構(gòu)造方法:
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); } }由于只是調(diào)用了父類BaseDexClassLoader的構(gòu)造方法,所以每個(gè)參數(shù)的含義將會(huì)留到BaseDexClassLoader再分析。
BaseDexClassLoader
PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader,其中的主要邏輯都是在BaseDexClassLoader完成的。
-
先來(lái)填下上文留下的坑,看看BaseDexClassLoader的構(gòu)造方法:
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()); } }BaseDexClassLoader的構(gòu)造函數(shù)包含四個(gè)參數(shù),分別為:
- dexPath:指目標(biāo)類所在的APK或jar文件的路徑,類裝載器將從該路徑中尋找指定的目標(biāo)類,該類必須是APK或jar的全路徑。如果要包含多個(gè)路徑,路徑之間必須使用特定的分割符分隔,分隔符通常為":"。
- optimizedDirectory:由于dex文件被包含在APK或者Jar文件中,因此在裝載目標(biāo)類之前需要先從APK或Jar文件中解壓出dex文件,該參數(shù)就是制定解壓出的dex 文件存放的路徑。如果該參數(shù)為null,則設(shè)置默認(rèn)路徑為/data/dalvik-cache 目錄。
- libraryPath:指目標(biāo)類中所使用的C/C++庫(kù)存放的路徑,多個(gè)路徑也是以“:”分隔。
- parent:父類加載器,遵從雙親委派。
-
在BaseDexClassLoader中的成員變量
private final DexPathList pathList十分重要,ClassLoader中的抽象方法findClass()、findResource()、findResources()、findLibrary()均是基于 pathList 來(lái)實(shí)現(xiàn)的(省略了部分源碼):@Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); ... return c; } @Override protected URL findResource(String name) { return pathList.findResource(name); } @Override protected Enumeration<URL> findResources(String name) { return pathList.findResources(name); } @Override public String findLibrary(String name) { return pathList.findLibrary(name); }那我們來(lái)看看DexPathList中做了什么。
DexPathList
-
在DexPathList中有個(gè)
private Element[] dexElements是它的重點(diǎn),Element是DexPathList的內(nèi)部類,有下面的成員變量:static class Element { private final File path; private final DexFile dexFile; private ClassPathURLStreamHandler urlHandler; private boolean initialized; } -
讓我們看看Element數(shù)組是如果生成的:
//在DexPathList構(gòu)造方法中調(diào)用makeDexElements方法生成 public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) { ... //splitDexPath()方法是把String切割成多個(gè)地址,再把每個(gè)地址生成File,該方法返回List<File> this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext); ... }private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader) { Element[] elements = new Element[files.size()]; int elementsPos = 0; //打開(kāi)所有文件并預(yù)先加載(直接或包含)dex文件 for (File file : files) { if (file.isDirectory()) { // 如果是文件夾,則直接添加 Element,這個(gè)一般是用來(lái)處理 native 庫(kù)和資源文件 elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); if (name.endsWith(DEX_SUFFIX)) { // 直接是.dex文件,而不是zip/jar文件(apk歸為zip),則直接加載dex文件 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 { //如果是zip/jar文件(apk歸為zip),加載dex文件。 DexFile dex = null; try { dex = loadDexFile(file, optimizedDirectory, loader, elements); } catch (IOException suppressed) { suppressedExceptions.add(suppressed); } //如果dex為空則不傳進(jìn)Element,file文件是肯定會(huì)傳進(jìn)的 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; }DexPathList.loadDexFile()方法最終會(huì)調(diào)用 JNI 層的方法來(lái)讀取 dex 文件,這里不再深入探究,有興趣的可以閱讀 從源碼分析 Android dexClassLoader 加載機(jī)制原理 這篇文章深入了解。 -
獲得了Element數(shù)組就可以通過(guò)DexPathList.findClass()方法來(lái)對(duì)類進(jìn)行加載了,源碼如下:
public Class<?> findClass(String name, List<Throwable> suppressed) { // 遍歷 dexElements 數(shù)組,依次尋找對(duì)應(yīng)的 class,一旦找到就終止遍歷 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; }? 這里有關(guān)于熱修復(fù)實(shí)現(xiàn)的一個(gè)點(diǎn),就是將補(bǔ)丁 dex 文件放到 dexElements 數(shù)組前面,這樣在加載 class 時(shí),優(yōu)先找到補(bǔ)丁包中的 dex 文件,加載到 class 之后就不再尋找,從而原來(lái)的 apk 文件中同名的類就不會(huì)再使用,從而達(dá)到修復(fù)的目的。
ClassLoader
-
ClassLoader是所有ClassLoader的最終父類。我們來(lái)瞧瞧ClassLoader的源碼:
public abstract class ClassLoader { static private class SystemClassLoader { public static ClassLoader loader = ClassLoader.createSystemClassLoader(); } //父加載器 private final ClassLoader parent; private static ClassLoader createSystemClassLoader() { String classPath = System.getProperty("java.class.path", "."); String librarySearchPath = System.getProperty("java.library.path", ""); //可以看出構(gòu)造PathClassLoader傳入了BootClassLoader return new PathClassLoader(classPath, librarySearchPath,BootClassLoader.getInstance()); } public static ClassLoader getSystemClassLoader() { return SystemClassLoader.loader; } private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; } protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } protected ClassLoader() { //外界沒(méi)有傳入指定父加載器的情況 this(checkCreateClassLoader(), getSystemClassLoader()); } public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ // 檢查是否已經(jīng)加載過(guò) Class<?> c = findLoadedClass(name); if (c == null) { // 沒(méi)有被加載過(guò) // 首先委派給父類加載器加載 try { if (parent != null) { //父加載器不為空則調(diào)用父加載器的loadClass 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) { // 如果父類加載器無(wú)法加載,才嘗試加載 c = findClass(name); } } return c; } private Class<?> findBootstrapClassOrNull(String name){ return null; } ... }? 從上面可以看出Android中的ClassLoader和Java中的區(qū)別并不大,ClassLoader的構(gòu)造方法也是分為指定parent和不指定parent兩種,不同的是在外界不指定parent的情況下,會(huì)通過(guò)
createSystemClassLoader()來(lái)獲取到PathClassLoader作為parent。直白的說(shuō),一個(gè)ClassLoader創(chuàng)建時(shí)如果沒(méi)有指定parent,那么它的parent默認(rèn)就是PathClassLoader,且此PathClassLoader父構(gòu)造器為BootClassLoader。? 可以看到Android中的ClassLoader.loadClass()和Java中的基本是不變的,都是實(shí)現(xiàn)了雙親委托。甚至就連在Java中調(diào)用BootstrapClassLoader的findBootstrapClassOrNull方法也保留著,然而android中并沒(méi)有BootstrapClassLoader,而且并沒(méi)有出現(xiàn)因?yàn)槟硞€(gè)ClassLoader不是Java實(shí)現(xiàn)的而導(dǎo)致無(wú)法持有父加載器的情況。。。所以在這里該方法直接返回nuil。
雙親委派
通過(guò)從ClassLoader.loadClass()方法中我們可以明白Android ClassLoader中的雙親委派流程。
- Android 雙親委派機(jī)制.jpg
-
帶上DexClassLoader一起玩雙親委派:
? ClassLoader的構(gòu)造方法中有一個(gè)參數(shù)是parent,那么是不是有辦法把PathClassLoader的parent替換成我們想要的DexClassLoader,在把DexClassLoader的parent設(shè)置成BootClassLoader,再加上父委托的機(jī)制,查找類的過(guò)程就變成BootClassLoader->DexClassLoader->PathClassLoader,這樣我們就能夠通過(guò)雙親委派先去加載外部apk的類了。我們可以通過(guò)反射來(lái)實(shí)現(xiàn)我們的設(shè)想。
public static void loadApk(Context context, String apkPath) { File dexFile = context.getDir("dex", Context.MODE_PRIVATE); File apkFile = new File(apkPath); //獲取到PathClassLoader ClassLoader classLoader = context.getClassLoader(); //創(chuàng)建DexClassLoader并設(shè)置父加載器為BootClassLoader DexClassLoader dexClassLoader = new DexClassLoader(apkFile.getAbsolutePath(), dexFile.getAbsolutePath(), null, classLoader.getParent()); try { //通過(guò)反射獲取到PathClassLoader的parent成員變量 Field fieldClassLoader = ClassLoader.class.getDeclaredField("parent"); if (fieldClassLoader != null) { //把parent成員變量賦值為DexClassLoader fieldClassLoader.setAccessible(true); fieldClassLoader.set(classLoader, dexClassLoader); } } catch (Exception e) { e.printStackTrace(); } }? 這樣就實(shí)現(xiàn)了DexClassLoader的插入,每次加載app的類之前都會(huì)通過(guò)DexClassLoader指定的位置查找是否有要用來(lái)覆蓋的類。
? 新的雙親委派流程圖如下:
dexclassloader加入雙親委派.png
參考
? Android動(dòng)態(tài)加載之ClassLoader詳解

