从源码中能够看到,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的双亲委托模型在热修复中的应用。