在應用程序打包成APK時,程序中所建立的類、導入和引用的類都會被編譯打包成一個或多個的dex文件中,打包成的dex文件在使用中如何加載類?答案就在本篇主題ClassLoader中,ClassLoader從字面就能夠知道它主要用於類的加載,當代碼中須要調用某個類時,ClassLoader就會遍歷全部的dex文件中的類並保存在集合中,而後從集合中加載指定的Class文件,最終轉換成JVM中使用的類;java
var loader = classLoader
while (loader != null) {
System.out.println(loader)
loader = loader.parent
}
複製代碼
2019-08-29 13:13:10.444 29022-29022/com.alex.kotlin.optimization I/System.out: dalvik.system.PathClassLoader
[DexPathList[
[zip file "/data/app/com.alex.kotlin.optimization-lLeC3751i-Krivn3eNgrYA==/base.apk"],
nativeLibraryDirectories=[/data/app/com.alex.kotlin.optimization-lLeC3751i-Krivn3eNgrYA==/lib/arm64, /system/lib64, /system/vendor/lib64]]]
2019-08-29 13:13:10.444 29022-29022/com.alex.kotlin.optimization I/System.out: java.lang.BootClassLoader@14d954f
複製代碼
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
複製代碼
ClassLoader 傳遞性 虛擬機的加載策略:在觸發虛擬機對類的加載時,虛擬機默認使用調用對象中的ClassLoader加載,而調用對象又被調用它的對象中的ClassLoader加載,按此傳遞最終執行到最上層的ClassLoader數組
雙親委派機制緩存
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
Class<?> c = findLoadedClass(name); //查找是否加載過此類
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false); //調用父類ClassLoader加載
} else {
c = findBootstrapClassOrNull(name); //父類爲null,表示爲BootstrapClassLoader
}
} catch (ClassNotFoundException e) {
}
if (c == null) { //父類查找爲null,調用本身的查找
c = findClass(name);
}
}
return c;
}
複製代碼
工做流程:安全
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
//BootClassLoader 中實現的方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null); //調用native方法
}
複製代碼
由上面的分析知道PathClassLoader和DexClassLoader都是BaseDexClassLoader的自類,PathClassLoader用於加載系統和app中的文件,在Zygote進程中啓動系統服務時建立,DexClassLoader負責加載指定目錄中的文件,從兩者的代碼中也能夠看出都是直接使用BaseDexClassLoader中的方法,因此程序中類的加載基本都是BaseDexClassLoader在工做,如下一塊兒看看BaseDexClassLoader的工做原理cookie
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);//建立DexPathList實例
}
複製代碼
從代碼中看出BaseDexClassLoader的構造函數中建立DexPathList的實例,將然如的參數封裝在DexPathList當中,其實這裏不僅是建立實例而是執行了整個過程,先看看傳入這幾個參數的意義:app
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions); // 委託pathList查找類文件
return c;
}
複製代碼
在findClass()方法中調用pathList的方法,因此整個邏輯的執行好像都在pathList中,這裏先來看看DexPathList中的基本屬性,具體看代碼中註釋:ide
private static final String DEX_SUFFIX = ".dex」; //dex文件後綴
private static final String zipSeparator = "!/「; //zip文件後綴
private final ClassLoader definingContext; //執行加載的ClassLoader,在建立時賦值
private Element[] dexElements; //查找以後保存的Element數組
private final Element[] nativeLibraryPathElements; // 本地的Elements列表
private final List<File> nativeLibraryDirectories;
private final List<File> systemNativeLibraryDirectories;
private IOException[] dexElementsSuppressedExceptions;
複製代碼
在DexPathList的構造函數中直接調用了makeDexElements(),整個加載解析的過程都在這個方法中,會遍歷文件集合中的文件,找出jar、apk、dex文件並保存在Element中,最後返回Element數組;函數
private static Element[] makeElements(List<File> files, File optimizedDirectory,
285 List<IOException> suppressedExceptions,
286 boolean ignoreDexFiles,
287 ClassLoader loader) {
288 Element[] elements = new Element[files.size()]; //建立Element集合
289 int elementsPos = 0;
294 for (File file : files) { //循環便利全部的File
295 File zip = null; //建立Zip、dir、DexFile實例
296 File dir = new File("");
297 DexFile dex = null;
298 String path = file.getPath(); //獲取文件的路徑和名稱
299 String name = file.getName();
300
301 if (path.contains(zipSeparator)) { //處理zip文件後綴
302 String split[] = path.split(zipSeparator, 2);
303 zip = new File(split[0]);
304 dir = new File(split[1]);
305 } else if (file.isDirectory()) {
308 elements[elementsPos++] = new Element(file, true, null, null); //保存目錄文件到Elements集合中
309 } else if (file.isFile()) {
310 if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) { //處理.dex文件後綴
312 try {
313 dex = loadDexFile(file, optimizedDirectory, loader, elements); //建立DexFile實例
314 } catch (IOException suppressed) {
316 suppressedExceptions.add(suppressed);
317 }
318 } else {
319 zip = file;
321 if (!ignoreDexFiles) {
322 try {
323 dex = loadDexFile(file, optimizedDirectory, loader, elements);
324 } catch (IOException suppressed) {
332 suppressedExceptions.add(suppressed);
333 }
334 }
335 }
336 }
339
340 if ((zip != null) || (dex != null)) { //保存Element
341 elements[elementsPos++] = new Element(dir, false, zip, dex);
342 }
343 }
344 if (elementsPos != elements.length) {
345 elements = Arrays.copyOf(elements, elementsPos);
346 }
347 return elements;
348 }
複製代碼
具體執行細節見代碼中註釋,這裏簡單總結一下:makeElements()方法中遍歷傳入的文件集合,查找集合中全部的apk、jar、dex文件及文件夾下全部的dex文件,對於文件目錄直接建立Element封裝文件,對於dex、apk、jar文件則建立DexFile封裝文件、ClassLoader、本地目錄、Elements信息,而後將DexFile和文件封裝成Element保存,由於最後的文件加載是獲取Elements的集合中保存Element實例,而後調用實例中的DexFile進行加載;工具
public Class findClass(String name, List<Throwable> suppressed) {
414 for (Element element : dexElements) { //遍歷DexFileelements數組
415 DexFile dex = element.dexFile; //獲取Element中的DexFile
417 if (dex != null) { //調用DexFile方法加載Class類,對於目錄來講此時dex = null
418 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
419 if (clazz != null) {
420 return clazz;
421 }
422 }
423 }
424 if (dexElementsSuppressedExceptions != null) {
425 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
426 }
427 return null;
428 }
複製代碼
由前面學習知道Class的加載是在DexPathList.findClass()中執行的,在findClass()中主要助興:源碼分析
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,DexFile dexFile) 複製代碼
由ClassLoader加載機制知道,再查找類時無論子類或是父類只返回最早查到的一個,即DexPathList中保存Elemets集合中靠前的一個,並且系統提供了DexClassLoader讓咱們本身加載dex文件,那麼從這原理咱們能夠發現彷佛咱們有操做和替換系統類的機會,也的確如此,熱修復的原理就是替換ClassLoader中解析出的Elements中順序,讓修復後的類被優先加載,從而拋棄有Bug的類,如QQ的超級補丁
將這裏根據上面的知識編寫dex文件加載工具類,在編寫代碼以前先分析一下代碼執行的邏輯:
//建立ClassLoader傳入dex文件路徑便可完成加載
DexClassLoader dexClassLoader = new DexClassLoader(path, optDir, path, getPathClassLoader());
複製代碼
Object elementsNew = getElements(getPathList(dexClassLoader));
//從dexClassLoader實例中建立的pathList
private static Object getPathList(BaseDexClassLoader classLoader) {
Object o = null;
try {
Field pathList = classLoader.getClass().getField("pathList"); //反射獲取pathList
o = pathList.get(classLoader);
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
// 從PathList中獲取加載的Elements數組
private static Object getElements(Object pathList) {
Object o = null;
try {
Field pathListField = pathList.getClass().getField("dexElements");//反射
o = pathListField.get(pathList);
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
複製代碼
Object elements = getElements(getPathList(getPathClassLoader()));
private static PathClassLoader getPathClassLoader() {
return (PathClassLoader) DexUtils.class.getClassLoader();
}
複製代碼
private static Object combineArray(Object firstArray, Object secondArray) {
Class<?> localClass = firstArray.getClass().getComponentType();
int firstArrayLength = Array.getLength(firstArray);
int allLength = firstArrayLength + Array.getLength(secondArray);
Object result = Array.newInstance(localClass, allLength);
for (int k = 0; k < allLength; ++k) {
if (k < firstArrayLength) {
Array.set(result, k, Array.get(firstArray, k));
} else {
Array.set(result, k, Array.get(secondArray, k - firstArrayLength));
}
}
return result;
}
複製代碼
private static void setElements(Object pathList, Object o) {
try {
Field pathListField = pathList.getClass().getField("dexElements");
pathListField.set(pathList, o);
} catch (Exception e) {
e.printStackTrace();
}
}
複製代碼
到此關於ClassLoader的介紹就結束了,認識ClassLoader加載機制對熱修復的學習有很大的幫助,本篇也做爲熱修復學習的第一篇,以後會繼續更新熱修復的學習;