Android中最主要的類加載器有以下4個:java
一個app必定會用到BootClassLoader、PathClassLoader這2個類加載器,可經過以下代碼進行驗證:android
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
ClassLoader classLoader = getClassLoader();
if (classLoader != null) {
Log.e("lqr", "classLoader = " + classLoader);
while (classLoader.getParent() != null) {
classLoader = classLoader.getParent();
Log.e("lqr", "classLoader = " + classLoader);
}
}
}
複製代碼
日誌輸出結果以下:c++
上面代碼中能夠經過上下文拿到當前類的類加載器(PathClassLoader),而後經過getParent()獲得父類加載器(BootClassLoader),這是因爲Android中的類加載器使用的是雙親委派模型。數組
雙親委派模型:緩存
在加載一個字節碼文件時,會詢問當前的classLoader是否已經加載過此字節碼文件。若是加載過,則直接返回,再也不重複加載。若是沒有加載過,則會詢問它的Parent是否已經加載過此字節碼文件,一樣的,若是已經加載過,就直接返回parent加載過的字節碼文件,而若是整個繼承線路上的classLoader都沒有加載過,才由child類加載器(即,當前的子classLoader)執行類的加載工做。安全
顯然,若是一個類被classLoader繼承線路上的任意一個加載過,那麼在之後整個系統的生命週期中,這個類都不會再被加載,大大提升了類的加載效率。cookie
一些Framework層級的類一旦被頂層classLoader加載過,會緩存到內存中,之後在任何地方用到,都不會去從新加載。網絡
共同繼承線程上的classLoader加載的類,確定不是同一個類,這樣能夠避免某些開發者本身去寫一些代碼冒充核心類庫,來訪問核心類庫中可見的成員變量。如java.lang.String在應用程序啓動前就已經被系統加載好了,若是在一個應用中可以簡單的用自定義的String類把系統中的String類替換掉的話,會有嚴重的安全問題。app
驗證多個類是同一個類的成立條件:ide
經過閱讀ClassLoader的源碼來驗證雙親委派模型。
找到ClassLoader這個類中的loadClass()方法,它調用的是另外一個2個參數的重載loadClass()方法。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
複製代碼
找到最終這個真正的loadClass()方法,下面即是該方法的源碼:
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;
}
複製代碼
能夠看到,如前面所說,加載一個類時,會有以下3步:
以上就是雙親委派模型的核心。在loadClass()中,調用了一個很重要的方法,那就是findClass(),去查找要加載的類。
在ClassLoader中,findClass()是空實現,這說明具體的方法會在子類中去重寫實現。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
複製代碼
因而,找到其子類BaseDexClassLoader,發現,AS實際上看不到系統級源碼。
這種狀況,在本人以前的《熱修復——深刻淺出原理與實現》文章中也有所說起,能夠藉助第三方源碼網站上查看,如:
PathClassLoader和DexClassLoader是BaseDexClassLoader的子類,源碼不多,就先查閱這2個類,再去研讀BaseDexClassLoader。
/** * A class loader that loads classes from {@code .jar} and {@code .apk} files * containing a {@code classes.dex} entry. This can be used to execute code not * installed as part of an application. * ... */
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
複製代碼
DexClassLoader的構造函數:
再回過頭來看DexClassLoader類上的註釋,大概翻譯就是說,DexClassLoader能夠加載jar包和apk包內dex文件中的類,能夠被用來執行非安裝過的app中的代碼。
這句註釋實際上是很重要的,它就是騰訊Tinker這一類熱修復解決方案的核心。一句話:能夠加載任意路徑下的dex文件。
/** * Provides a simple {@link ClassLoader} implementation that operates on a list * of files and directories in the local file system, but does not attempt to * load classes from the network. Android uses this class for its system class * loader and for its application class loader(s). */
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);
}
}
複製代碼
PathClassLoader的構造函數:
相比DexClassLoader的構造函數,PathClassLoader的構造函數少了一個參數libraryPath,這也就致使了PathClassLoader只能加載已安裝應用內dex中的class,從類上的說明中也能夠了解到,只能加載本地應用中的類,不能加載網絡上的類。
一句話,PathClassLoader只能用於加載已安裝應用的dex文件。
看完DexClassLoader和PathClassLoader,發現它們根本沒有對findClass()這個方法進行重寫,說明它們的findClass()方法確定在其父類BaseDexClassLoader中進行了統一實現處理。
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, 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;
}
}
...
複製代碼
能夠發現,實際上BaseDexClassLoader並無實現查找類的具體邏輯,它只是一箇中轉,調用的是DexPathList的findClass()方法,而這個DexPathList對象是在BaseDexClassLoader構造函數中進行實例化,並保存了幾個BaseDexClassLoader會用到的屬性,注意,DexPathList保存的optimizedDirectory可能爲空,到時走的是PathClassLoader的邏輯。因此,下面就來看DexPathList:
final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private final ClassLoader definingContext;
/** * List of dex/resource (class path) elements. * Should be called pathElements, but the Facebook app uses reflection * to modify 'dexElements' (http://b/7726934). */
private final Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {
this.definingContext = definingContext;
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
}
複製代碼
天然下面就得先了解下這個Element和makeDexElements()方法。
static class Element {
private final File file;
private final boolean isDirectory;
private final File zip;
private final DexFile dexFile;
private ZipFile zipFile;
private boolean initialized;
public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
this.file = file;
this.isDirectory = isDirectory;
this.zip = zip;
this.dexFile = dexFile;
}
...
}
複製代碼
Element是PathList的靜態內部類,其中,DexFile dexFile這個屬性是最關鍵的。接下來是makeDexElements()方法:
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
/* * Open all files and load the (direct or contained) dex files * up front. */
for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();
if (file.isDirectory()) {
// We support directories for looking up resources.
// This is only useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else if (file.isFile()){
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else {
zip = file;
dex = loadDexFile(file, optimizedDirectory);
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
複製代碼
它對files集合進行遍歷(這個files集合就是dexPath下全部的文件及目錄),來看該方法對文件是怎麼處理的:它不論是dex文件,或是壓縮包文件,都會調用到loadDexFile()方法:
private static DexFile loadDexFile(File file, File optimizedDirectory) throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
複製代碼
因此,若是optimizedDirectory爲null,說明這是PathClassLoader的處理方式,直接將file封裝成DexFile對象返回;若是optimizedDirectory不爲null,說明這是DexClassLoader的處理方式,若file是dex文件就封裝成DexFile對象返回,若file是壓縮包,會先進行解壓,將其中的dex文件封裝成DexFile對象返回。反正,不論是哪一種方式,就終都是獲得dex文件對象,而且,在makeDexElements()方法的最後,添加進Element數組中。
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
複製代碼
終於到最後一個findClass()方法了,其實它就是遍歷dex文件數組(dexElements),獲得一個個的dex文件對象,調用其loadClassBinaryName()方法通用類名找到類,快接近真相了,下面就看看DexFile中究竟是怎麼經過類名找到類的,堅持~
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, long cookie, List<Throwable> suppressed) {
Class result = null;
result = defineClassNative(name, loader, cookie);
return result;
}
private static native Class defineClassNative(String name, ClassLoader loader, long cookie) throws ClassNotFoundException, NoClassDefFoundError;
複製代碼
在DexFile這個類中,loadClassBinaryName()調用了defineClass(),最終調用的是defineClassNative()這個native方法,也就是說,類的加載最終是用c/c++的方式來進行處理的,由於是native方法,這裏就沒辦法繼續往下跟了,所以,其真實處理邏輯咱們就不得而知了。
可是,聯想到前面的《熱修復與插件化基礎——dex與class》文章中提到的dex頭文件中包含了該dex中全部class的信息,因此,咱們不妨能夠大膽猜測一下,其實defineClassNative()這個native方法應該就是經過讀取dex頭文件的方式找到並定義了class。
所謂一圖勝千言,經過上面一系列的方法跟蹤,及流程梳理,最終,獲得以下這張圖: