类加载器ClassLoader和双亲委托模型

基本信息

在Java中,JVM虚拟机通过ClassLoader来加载.jar文件里面的类。在Android中,Android用的是Dalvik虚拟机或者ART虚拟机来加载.dex文件里的类。

类加载器ClassLoader

ClassLoader就像其名字一样,是专门用来处理类的加载工作的,程序基本上都是由类组成的,运行程序也就是运行类,也就是运行编译.java文件得到的.class文件。JVM 将Java类文件加载到内存中,而一个Android 的App是由Dalvik/ART虚拟机来将类加载到内存中的,因此可以在程序运行的时候手动加载Class,从而达到代码动态加载可执行文件的目的,这就要通过ClassLoader类加载器了。

获取ClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ClassLoader classLoader = getClassLoader();
if (classLoader != null){
Log.i("ClassLoader", "classLoader " + " : " + classLoader.toString());
while (classLoader.getParent()!=null){
classLoader = classLoader.getParent();
Log.i("ClassLoader" ,"classLoader " + " : " + classLoader.toString());
}
}
}

输出:

1
2
classLoader : dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.rocka.classloader/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
classLoader : java.lang.BootClassLoader@36af4e44

从侧面证明,一个运行中的Android APP至少2个ClassLoader,一个是BootClassLoader(系统启动的时候创建的),另一个是PathClassLoader(应用启动时创建的,用于加载“/data/app/com.rocka.classloader/base.apk”里面的类)

自定义ClassLoader

查看ClassLoader的构造方法,发现每次都需要传入父类的加载器,它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)。

1
2
3
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}

双亲代理模式

双亲代理模型(Parent-Delegation Model)这个名词一般是出现在Java类加载机制中的,Java 虚拟机将Java类文件通过ClassLoader加载到JVM内存中。

  • 三个级别,三种类型的加载

    1. 启动级: BootStrapClassLoader(启动加载器),它负责将Java_Home/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,不允许直接通过引用进行操作。
    2. 拓展级: ExtensionClassLoader(标准扩展加载器),它负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
    3. 应用级: AppClassLoader(应用内加载器),负责将路径中指定类库加载到内寸中。
  • 操作过程

    当加载某个类的时候,会先判断是否加载了该类如果没有,会要求应用级来加载,应用级请求拓展级来加载,拓展级来请求启动级,由最高级别的启动级来负责。这个就是双亲委托,委托上两级来处理类的加载,如果上一级无法进行处理就给下级进行处理,依次类推,双亲不行,就由自己来加载。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
    //check the class has been loaded or not
    Class c = findLoadedClass(name);
    if(c == null){
    try{
    if(parent != null){
    c = parent.loadClass(name,false);
    }else{
    c = findBootstrapClassOrNull(name);
    }
    }catch(ClassNotFoundException e){
    //if throws the exception ,the father can not complete the load
    }
    if(c == null){
    c = findClass(name);
    }
    }
    if(resolve){
    resolveClass(c);
    }
    return c;
    }

java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

  • 为什么使用双亲委托

    比如系统已经有的一个类,java.lang.Object。你在自己的项目中也建一个同名的java.lang.Object的类,这个时候进行类的加载是由bootstrap级的loader来进行加载的,这个时候发现了已经加载了Object这个类,那么就不会加载自定义的Object类,所以同名的类,可以正常编译,不能正常加载,保证了安全性,避免了用户自己的代码冒充核心类库。

DexClassLoader和PathClassLoader源码分析

ClassLoader是一个抽象类,一般在开发使用的时候是使用DexClassLoader和PathClassLoader类加载器来加载类,两者只是简单的对BaseDexClassLoader做了一下封装。

  • DexClassLoader可以加载的类型有jar,dex,apk,可以从SD卡中加载未安装的apk
  • PathClassLoader只能加载已经安装过的apk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}

// PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}

public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}

具体操作其实还是在父类的BaseDexClassLoader,主要区别在第二个参数,DexClassLoader有传值,PathClassLoader没有传值。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* dexPath: 需要被加载的dex文件地址,可以多个,用File.pathSeparator分割
* optimizedDirectory: 优化之后的dex存放路径,不可以为null。
* libraryPath:包含libraries的目录列表,同样用File.pathSeparator分割,如果没有则传null就行了
* parent: 父类的类加载器
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.originalPath = dexPath;
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

接下来看看DexPathList的源码

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
/**
* @param optimizedDirectory optimizedDirectory 如果为空,那么使用系统默认的文件夹
*/
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
//赋值了一个dexElements的数组,存放我们dex文件的数组
this.dexElements =
makeDexElements(splitDexPath(dexPath), optimizedDirectory);
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}

这就是PathClassLoader传空,因为应用已经安装并且优化了,优化后的dex存在于/data/dalvik-cache目录下,这就是系统默认的文件夹,这也就是PathClassLoader加载已经安装的apk的,DexClassLoader可以加载未安装的原因。

makeDexElements

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {
List<Element> elements = new ArrayList<>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();

if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
zip = new File(split[0]);
dir = new File(split[1]);
} else if (file.isDirectory()) {
// We support directories for looking up resources and native libraries.
// Looking up resources in directories is useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else if (file.isFile()) {
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 {
zip = file;

try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}

if ((zip != null) || (dex != null)) {
elements.add(new Element(dir, false, zip, dex));
}
}

return elements.toArray(new Element[elements.size()]);
}

创建Element类型的List,并且加载Dex文件,将Elment类型加入List。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;

if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}

每一个item对应一个dex文件。DexPathList遍历dexElements,从每一个dex文件中查找目标类,在找到后即返回并停止遍历,并且调用dex.loadClassBinaryName()方法。

1
2
3
4
public Class loadClassBinaryName(String name, ClassLoader loader) {
return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);

最后一步,调用了Native方法defineClass加载类。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器