從源碼中能夠看到,Android中有三個ClassLoader,分別是BaseDexClassLoader、PathClassLoader、DexClassLoader。 java
從上圖能夠看出,ClassLoader的直接子類是BaseDexClassLoader、SecureClassLoader;間接子類是DelegateLastClassLoader、DexClassLoader、InMemoryDexClassLoader、PathClassLoader、URLClassLoader。 咱們比較經常使用的是BaseDexClassLoader、DexClassLoader和PathClassLoader。數組
DexClassLoader和PathClassLoader都繼承自BaseDexClassLoader。DexClassLoader和PathClassLoader的主要功能都放在了BaseDexClassLoader中。看一下BaseDexClassLoader的構造函數:安全
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
複製代碼
參數有四個,分別是: String dexPath,指要加載的apk或者jar包的路徑。最終要將該dexPath路徑上的文件ODEX優化到optimizedDirectory文件夾中,而後再對ODEX後的文件進行加載。 File optimizedDirectory,dexPath路徑上的文件須要優化到的文件夾。 String libraryPath,apk或者jar包中的C或者C++庫所存放的路徑。 ClassLoader parent,該ClassLoader的父類ClassLoader,通常爲當前執行類的ClassLoader。bash
PathClassLoader繼承自BaseDexClassLoader,看一下它的構造函數:ide
public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
複製代碼
構造函數直接調用了BaseDexClassLoader的構造函數,注意其中的第二個參數傳了null。函數
DexClassLoader繼承自BaseDexClassLoader,看一下它的構造函數:優化
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), library, parent);
}
複製代碼
構造函數直接調用了BaseDexClassLoader的構造函數,咱們對比一下PathClassLoader的構造函數,能夠看到除了第二個參數不一樣以外,其餘參數都是同樣的。 而咱們知道PathClassLoader只能夠加載Android系統類和應用的類。而DexClassLoader不單單能夠加載Android系統類和應用的類,而且能夠加載外部jar包和Apk包的類。 所以,咱們猜測可能跟第二個參數不一致致使的。 再返回到BaseDexClassLoader中看第二個參數的做用,能夠看到在BaseDexClassLoader中使用optimizedDirectory構造了DexPathList對象。那麼咱們來分析一下DexPathList這個類。ui
首先看到DexPathList的構造函數this
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
。。。省略
this.definingContext = definingContext;
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
複製代碼
其中optimizedDirectory參數用做了makeDexElements方法的參數,那咱們再去看makeDexElements方法。spa
private static Element[] makeDexElements(ArrayList<File> files,
File optimizedDirectory) {
ArrayList<Element> elements = new ArrayList<Element>();
for (File file : files) {
ZipFile zip = null;
DexFile dex = null;
String name = file.getName();
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 if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
try {
zip = new ZipFile(file);
} catch (IOException ex) {
}
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ignored) {
}
} else {
System.logW("Unknown file type for: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
複製代碼
這裏又調用了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時,直接new了一個DexFile對象,而不爲null時,調用了DexFile的loadDex靜態方法。在DexFile的loadDex最終new了一個DexFile對象,而且在DexFile的構造函數中調用了openDexFile方法。該方法中的原型以下:
native private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException;
複製代碼
能夠看到PathClassLoader這裏應該爲null,而DexClassLoader這裏應該是dex優化後的路徑。因此PathClassLoader沒有設置Dex優化後的存放路徑。其實optimizedDirectory爲null時的默認路徑就是/data/dalvik-cache 目錄。 因此這就是PathClassLoader只能加載Android系統或者應用的類的緣由,而DexClassLoader能夠加載外部jar包或者Apk包的緣由。
Android中一共有三種ClassLoader,分別是BaseDexClassLoader、PathClassLoader、DexClassLoader。PathClassLoader和DexClassLoader都繼承於BaseDexClassLoader,PathClassLoader只能加載Android系統和應用內的類,而DexClassLoader除了能夠加載Android系統和應用內的類外,還能夠加載外部的apk和jar包。
Android的雙親委託模型就是當某個類加載器接到加載某個類的請求時,其首先會將加載任務委託給其父加載器,以此遞歸,若是父加載器能夠完成加載任務,則返回,不然再使用該加載器進行類的加載。
一、能夠避免類的重複加載。當父加載器已經加載了此類,子類加載器就不用再加載一遍,不然會造成類的重複加載。 二、考慮到安全方面,若是不使用雙親委託模型,那麼若是咱們自定義一個ClassLoader去加載一個系統類,例如java.lang.Object。若是沒有雙親委託模型,那麼咱們自定義的ClassLoader就會去加載這個類。
並非的,雙親委託模式只是JDK提供的ClassLoader類的實現方式。在實際開發中,咱們能夠經過自定義ClassLoader,並重寫父類的loadClass方法,就能夠打破這一機制。
熱修復目前有三種方案,分別是底層替換方案、類加載方案和Instant Run方案。 而雙親委託模型在熱修復上面的應用主要體如今類加載方案上面。因此咱們這裏重點看一下怎麼使用類加載方案來實現熱修復。 從上面ClassLoader加載類的過程當中,咱們知道類是由DexPathList中的findClass方法來加載類的。看一下findClass方法:
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {//1
Class<?> clazz = element.findClass(name, definingContext, suppressed);//2
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
複製代碼
Element內部封裝了DexFile,DexFile用於加載dex文件,因此dex文件和Element是一一對應的。當有多個dex文件時,就會有多個Element對象,造成Element數組。當須要查找類時,會去遍歷這個數組,其實至關於去遍歷dex文件數組,而後經過Element的findClass方法去查找類。當查找到相應的類後,就返回,不然繼續使用下一個Element來查找。 上面的加載流程其實就是雙親委託模型,因此咱們能夠將修復後的類patch.class打包成包含dex的補丁包,並將其放在Element數組的第一個位置,這樣當去查找該類的時候,就先會去修復後的dex中查找到修復後的類,然後面有問題的類並不會被查找到。這就是ClassLoader的雙親委託模型在熱修復中的應用。