Android中的ClassLoader

Android中的ClassLoader

Java中的ClassLoader可以加载jar包和class文件,在Android则不同。在Android中,不论DVM还是ART,加载的都是dex文件。Android中的ClassLoader可以分为系统类加载器和自定义加载器。

Android中ClassLoader类继承关系如下

BootClassLoader

源码

BootClassLoaderClassLoader的一个内部类,并继承自ClassLoader

它是包内可见的,因此我们没法使用它,也不能使用动态加载。

UrlClassLoader

UrlClassLoader继承自SecureClassLoader,它只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。

BaseDexClassLoader

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
if (reporter != null) {
reportClassLoaderChain();
}
}
public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexFiles);
}

BaseDexClassLoader继承自ClassLoader,用于加载各种dex中的类。

具体参数的含义

  • dexPath. 指目标类所在的APK或jar文件的路径,类装载器将从该路径中寻找指定的目标类,该类必须是APK或jar的全路径.如果要包含多个路径,路径之间必须使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)获得。最终做的是将dexPath路径上的文件ODEX优化到内部位置optimizedDirectory,然后,再进行加载的。
  • optimizedDirectory. 由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径。这也是对apk中dex根据平台进行ODEX优化的过程。其实APK是一个程序压缩包,里面包含dex文件,ODEX优化就是把包里面的执行程序提取出来,就变成ODEX文件,因为你提取出来了,系统第一次启动的时候就不用去解压程序压缩包的程序,少了一个解压的过程。这样的话系统启动就加快了。为什么说是第一次呢?是因为DEX版本的也只有第一次会解压执行程序到 /data/dalvik-cache(针对PathClassLoader)或者optimizedDirectory(针对DexClassLoader)目录,之后也是直接读取目录下的的dex文件,所以第二次启动就和正常的差不多了。当然这只是简单的理解,实际生成的ODEX还有一定的优化作用。ClassLoader只能加载内部存储路径中的dex文件,所以这个路径必须为内部路径。
  • librarySearchPath.指目标类中所使用的C/C++库存放的路径
  • parent. 是指该装载器的父装载器,一般为当前执行类的装载器,例如在Android中以context.getClassLoader()作为父装载器。
PathClassLoader

源码

1
2
3
4
5
6
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的构造方法中没有参数optimizedDirectory,这是因为PathClassLoader已经默认了参数optimizedDirectory的值为/data/dalvik-cache目录。所以PathClassLoader无法定义解压的dex文件的存储路径,因此PathClassLoader通常用来加载已经安装的apk的dex文件。

DexClassLoader

源码

1
2
3
4
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}

DexClassLoader可以加载dex文件以及包含dex的压缩文件,不管加载哪种文件,最终都是要加载dex文件。

InMemoryDexClassLoader

源码

1
2
3
4
5
6
7
public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
super(dexBuffers, parent);
}
public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {
this(new ByteBuffer[] { dexBuffer }, parent);
}

InMemoryDexClassLoader是API26的时候新增的。ByteBuffer数组构造了一个DexPathList,可用于内存中的dex文件。

ClassLoader 的双亲委托模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
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;
}
private Class<?> findBootstrapClassOrNull(String name){
return null;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

加载类的时候,首先判断有没有加载过,如果已经加载过了直接返回,否则就判断父加载器是否存在。如果存在父加载器,则调用父加载器的loadClass方法,不存在则调用findBootstrapClassOrNull方法。但是findBootstrapClassOrNull会直接返回null。所以最终又会调用到findClass方法。而findClass会直接抛出异常,所以这个需要子类来实现。这就是双亲委托模型。双亲委托模型一方面可以避免重复加载类,另一方面可以避免有人恶意编写一个类加载到JVM中。

Refer:https://www.jianshu.com/p/a620e368389a