类加载器ClassLoader和双亲委托模型
基本信息
在Java中,JVM虚拟机通过ClassLoader来加载.jar文件里面的类。在Android中,Android用的是Dalvik虚拟机或者ART虚拟机来加载.dex文件里的类。
类加载器ClassLoader
ClassLoader就像其名字一样,是专门用来处理类的加载工作的,程序基本上都是由类组成的,运行程序也就是运行类,也就是运行编译.java文件得到的.class文件。JVM 将Java类文件加载到内存中,而一个Android 的App是由Dalvik/ART虚拟机来将类加载到内存中的,因此可以在程序运行的时候手动加载Class,从而达到代码动态加载可执行文件的目的,这就要通过ClassLoader类加载器了。
获取ClassLoader
1 |
|
输出:
1 | classLoader : dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.rocka.classloader/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]] |
从侧面证明,一个运行中的Android APP至少2个ClassLoader,一个是BootClassLoader(系统启动的时候创建的),另一个是PathClassLoader(应用启动时创建的,用于加载“/data/app/com.rocka.classloader/base.apk”里面的类)
自定义ClassLoader
查看ClassLoader的构造方法,发现每次都需要传入父类的加载器,它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)。
1 | protected ClassLoader(ClassLoader parent) { |
双亲代理模式
双亲代理模型(Parent-Delegation Model)这个名词一般是出现在Java类加载机制中的,Java 虚拟机将Java类文件通过ClassLoader加载到JVM内存中。
三个级别,三种类型的加载
- 启动级: BootStrapClassLoader(启动加载器),它负责将Java_Home/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,不允许直接通过引用进行操作。
- 拓展级: ExtensionClassLoader(标准扩展加载器),它负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
- 应用级: AppClassLoader(应用内加载器),负责将路径中指定类库加载到内寸中。
操作过程
当加载某个类的时候,会先判断是否加载了该类如果没有,会要求应用级来加载,应用级请求拓展级来加载,拓展级来请求启动级,由最高级别的启动级来负责。这个就是双亲委托,委托上两级来处理类的加载,如果上一级无法进行处理就给下级进行处理,依次类推,双亲不行,就由自己来加载。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22protected 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 | // DexClassLoader.java |
具体操作其实还是在父类的BaseDexClassLoader,主要区别在第二个参数,DexClassLoader有传值,PathClassLoader没有传值。
1 | /** |
接下来看看DexPathList的源码
1 | /** |
这就是PathClassLoader传空,因为应用已经安装并且优化了,优化后的dex存在于/data/dalvik-cache目录下,这就是系统默认的文件夹,这也就是PathClassLoader加载已经安装的apk的,DexClassLoader可以加载未安装的原因。
makeDexElements
1 | /** |
创建Element类型的List,并且加载Dex文件,将Elment类型加入List。
1 | public Class findClass(String name, List<Throwable> suppressed) { |
每一个item对应一个dex文件。DexPathList遍历dexElements,从每一个dex文件中查找目标类,在找到后即返回并停止遍历,并且调用dex.loadClassBinaryName()方法。
1 | public Class loadClassBinaryName(String name, ClassLoader loader) { |
最后一步,调用了Native方法defineClass加载类。