在应用程序打包成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加载机制对热修复的学习有很大的帮助,本篇也做为热修复学习的第一篇,以后会继续更新热修复的学习;