1. ClassLoader
Android 中的 ClassLoader 加载的是 dex 文件。
Android 中的 ClassLoader 分为两种类型,分别是系统类加载器 和自定义加载器。
系统类加载器主要有三种:BootClassLoader 、PathClassLoader 和 DexClassLoader。
-
BootClassLoader
BootClassLoader 由 Java 实现的,是 ClassLoader 的内部类,用于加载 Android Framework 层的 class文件。
BootClassLoader是一个单例类,并且只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的。
-
PathClassLoader
用于加载 Android 应用程序类加载器。可以加载指定的 dex ,以及jar、zip、apk 中的classes.dex。
-
DexClassLoader
可以加载指定的 dex ,以及jar、zip、apk 中的classes.dex。
以下为 PathClassLoader 和 DexClassLoader 的构造方法;
java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent){
super(dexPath, null, librarySearchPath, parent);
}
}
PathClassLoader 和 DexClassLoader 都继承自 BaseDexClassLoader。二者唯一区别:创建 DexClassLoader 需要传递一个 optimizedDirectory 参数,并且会将其创建为 File 对象传给 super ,而 PathClassLoader 直接为 null。
optimizedDirectory 参数就是 dexopt 的产出目录。那 PathClassLoader 创建时,这个目录为 null,Android 5.0 以下, optimizedDirectory 为 null 时的默认路径为:/data/dalvik-cache
。
dexopt :
在Dalvik中虚拟机在加载一个dex文件时,对 dex 文件 进行 验证 和 优化的操作,其对 dex 文件的优化结果变成了 odex(Optimized dex) 文件,这个文件使用了一些优化操作码,和 dex 文件很像。
该目录在安装某个 APK 时,系统会自动在其中存放 odex 文件。而在使用PathClassLoader
加载时,对于/data/dalvik-cache
这个目录,我们的应用自身并不具备写的权限,因此 PathClassLoader 只能加载已经安装的APK中的dex文件。
而到了ART下,加载方式发生了截然不同的变化,在安装时对 dex 文件执行dex2oat(AOT 提前编译操作),编译为OAT 可执行文件。若在加载时,无法成功加载 oat 文件,则会从原 dex 中加载,因此 ART 下,PathClassLoader
与DexClassLoader
都能加载任意指定的dex,以及jar、zip、apk中的classes.dex。但是从原dex加载会导致无法dex2oat,加快加载速度,降低运行效率。
到了Android 8.1及以后,此时 DexClassLoader 中 optimizedDirectory 同样固定传递null,两者没有任何区别了。
2. ClassLoader 的继承关系
3. ClassLoader 的加载过程
双亲委托模式:首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次进行递归,直到委托到最顶层的 ClassLoader, 如果最顶层的 ClassLoader 找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。
双亲委托模式优点:
- 避免重复加载,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
- 安全性考虑,防止核心API库被随意篡改。
Android 的 ClassLoader 遵循了双亲委托模式 ,ClassLoader 的加载方法为抽象类 ClassLoader 中的 loadClass 方法:
java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//找缓存,找到return
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;
}
首先,在第5行处,找缓存中是否存在该类,findLoadedClass(name)
方法最终会调用到本地方法native static Class findLoadedClass(ClassLoader cl, String name)
,若传入的类已经加载,则返回该类。如不存在,则在第8行判断父加载器是否存在,第9行,parent 为 ClassLoader 对象,parent 为当前加载器的父类加载器,存在就调用父类加载器中的parent.loadClass(name, false)
方法。父类加载器不存在就调用findBootstrapClassOrNull(name)
方法,该方法会直接返回null。如果28行 c == null
,则说明向上委托流程没有检查出类已经被加载,接着执行21行 findClass(name)
方法自己来进行查找。
现在若要查看自己写的类是否被加载,自己定义的类在APP中,在 dex 文件中,通过 parent.loadClass(name, false)
是寻找不到的 ,最后通过 PathClassLoader 的 findClass(name)
方法寻找。
java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent){
super(dexPath, null, librarySearchPath, parent);
}
}
PathClassLoader 在初始化时传入了一个地址 dexPath,dexPath地址对应的就是当前程序对应的APK文件。所以 PathClassLoader 能加载到你自己在程序中定义的类。
查看 ClassLoader 中的 findClass 方法:
java
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
该方法需要子类进行实现,PathClassLoader 类中没有重写findClass 方法,在 PathClassLoader 的父类BaseDexClassLoader 中实现了 findClass 方法:
java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
//创建了DexPathList,地址 dexPath 传进DexPathList
this.pathList = new DexPathList(this, dexPath, librarySearchPath,
optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//查找指定的class
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 中是如何找到自定义的类?第14行代码,Class c = pathList.findClass(name, suppressedExceptions)
去寻找类,pathList又是什么呢?第6行代码,在 BaseDexClassLoader 的构造方法中创建了 pathList,并将 dex 地址 dexPath 传入,其实 pathList 是一个 DexPathList 对象。查看 DexPathList 类,在初始化
java
private Element[] dexElements;
//构造方法
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
//...
// splitDexPath 实现为返回 List<File>
// makeDexElements 会去 List<File>.add(dexPath) 中使用DexFile加载dex文件返回 Element数组
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
//...
}
在 DexPathList 的构造方法中,第9行,其中 splitDexPath
方法进行了分组操作,传入的 dex 文件地址可能有多个,将 dexPath 中不同的dex文件地进行拆分, 并返回一个 List<File> 集合。makeDexElements 方法返回的是一个 Element[]。一个dex文件就是一个Element对象,多个dex就是一个 Element[] 数组。这时再看 BaseDexClassLoader 中的 findClass 方法的pathList.findClass()
,该方法调用的是 DexPathList 中的 findClass 方法:
java
public Class findClass(String name, List<Throwable> suppressed) {
//从element中获得代表Dex的 DexFile
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
//查找class
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
方法第4行对 dexElements 数组进行循环访问,然后通过第6行DexFile dex = element.dexFile
拿到具体的dex文件,然后通过dex 再去寻找具体的类,找到了返回该类,没找到返回null。