Android中有哪几种ClassLoader?它们的作用和区别是什么?
BootClassLoader,URLClassLoader,PathClassLoader,DexClassLoader,BaseDexClassLoader等,这些最终都继承自java.lang.ClasssLoader
关系图如下:
Android 平台的ClassLoader
BootCLassLoader
和JVM不同的是,BootClassLoader是ClassLoader的内部类,由java代码实现而不是C++,是Android平台所有ClassLoader的最终Parent,这个类是包内可见,所以我们无法使用。
URLClassLoader
只能用于jar文件,但是由于Dalvik不能识别jar,所以在Android平台上无法使用这个加载器
BaseDexClassLoader
DexClassLoader和PathClassLoader 都继承BaseDexClassLoader,其中主要逻辑都是在BaseDexClassLoader完成的。是一个基础的加载器,
先看BaseDexClassLoader的构造函数
/**
* @param dexPath 指目标类所在的APK或jar文件路径,
* @param optimizedDirectory 优化目录
* @param libraryPath 指的是目标所使用的c/c++库存放的路径
* @param parent 指该加载器的父加载器,
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
四个参数的解释:
- dexPath 类加载器将从该路径中寻找指定的目标类,
- 该类必须是APK或者jar的全路径如果要包括多个路径,
- 路径之间必须使用指定的分隔符,可以使用System.getProperty("path.separtor"),
- DexClassLoader 中所谓的支持加载APK,jar,dex,也可以从SD卡中加载,指的就是这个路径,
- 最终是将dexPath路径上的文件ODEX优化到optimizedDirectoyr,然后进行加载
- optimizedDirectory
- 由于dex文件被包含在APK或者jar中,因此在装载目标类之前需要从APK或者JAR文件中解压dex文件,该参数就是定制解压出来的dex文件存放的路径的,这也是对apk的dex根据平台进行ODE优化的过程,
- 其实APK 是一个压缩包,里面包括dex文件,ODEX文件的优化就是把包里面的执行程序提取出来,就变成ODEX文件,因为你提取出来了,系统第一次启动的时候就不用去解压程序压缩包里面的程序,少了一个解压过程,这样系统启动就加快,
- 为什么说是第一次呢?因为DEX版本只会有在第一次会解压执行程序到/data/dalvik-cache(针对PathClassLoder)或者optimizedDirectory(针对DexClassLoader)目录,之后也就直接读取dex文件,所以第二次启动就和正常启动差不多了,当然这只是简单理解,实际生成ODEX还有一定的优化作用,
- ClassLoader只能加载内部存储路径中的dex文件,所以这个路径必须是内部路径
- libraryPath 指的是目标所使用的c/c++库存放的路径
- parent 一般为当前执行类的加载器,例如Android中以context.getClassLoader()作为父加载器
PathClassLoader
继承BaseDexClassLoader,构造函数如下
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);
}
}
很显然,PathClassLoader把optimizedDirectory设置为null,也就是没有设置优化后存放的路径,其实optimizedDirectory为null的默认路径就是/data/dalivk-cache目录
在Dalivk虚拟机上只能加载已安装的APK的dex,在ART虚拟机上,可以加载未按照的APK的dex
主要用于系统和app的类加载器
在应用启动的时候创建,从data/app/安装目录下加载apk文件
在Android中,app 安装到手机中,apk的dex文件中的class文件都是通过PathClassLoader来加载的
DexClassLoader
继承BaseDexClassLoader,构造函数如下
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
支持加载APK,JAR,和DEX,也可以从SD卡上进行加载
虽然说dalivk不能直接识别jar,但是却说DexClassLoader却能加载JAR文件,其实这个不矛盾,因为在BaseDexClassLoader里面对“.jar”,".apk",".zip",".dex"后缀的文件最后都会生成对应的dex文件,所以最终处理的还是dex文件,而URLClassLoader却没有做这样的处理,所以我们一般都是用DexClassLoader来作为动态加载的加载器
可以从包含classes.dex的jar或者APK 中加载类的加载器,用于执行动态加载,但必须是app私有可写目录来缓存ODEX 文件,能够加载系统没按照的apk或者jar文件,因此很多插件化方案都是用DexClassLoader
摘抄:
Android动态加载之ClassLoader详解
Android类加载器ClassLoader
简述ClassLoader的双亲委托模型
我们知道,每一个加载器都有一个父加载器,这个双亲委托模型就和这个父加载器有关:如果一个加载器收到了加载类的请求,他首先不会自己尝试去加载这个类,而是把这个请求交给他的父类加载器完成,父类加载器也有父类加载器的,每个加载器都是如此,只有当父加载器在自己搜索范围内找不到指定的类时( ClassNotFoundException),子加载器才会尝试自己去加载。
具体加载过程如下:
- 源ClassLoader判断是否加载过该class,如果加载过,则直接返回该Class,如果没有则委托父类加载,
- 父类加载器判断是否加载过该Class,如果加载过,则直接返回该Class,如果没有则委托父类的父类,也就是爷爷类加载器
- 以此类推,直到始祖类加载器(引用类加载器)
- 始祖类加载器判断是否加载过该Class,如果加载过,则直接返回该Class,如果没有,则尝试从其对应的类的路径下寻找class字节码并载入,如果载入成功,则直接返回Class,如果载入失败,则委托给始祖类的子类加下载器
- 始祖类的子类加载器尝试从其对应的类的路径下寻找class字节码并载入,如果载入成功,则直接返回该Class,如果载入失败,则委托给始祖类子类的子类,即孙子类加载器
- 以此类推,直到源ClassLoader
- 源ClassLoader尝试从其对应的类的路径下寻找class字节码并载入,如果载入成功,则直接返回该class,如果载入失败,源ClassLoader不会在委托其他子类加载器,而是抛出异常。
委托机制的意义:防止内存中出现多份同样的字节码
,例如如果A类和B类都需要加载一个System,如果不用委托机制,而是自己加载自己,那么A类会加载一份System字节码.B 类又会加载一份System 字节码,这样内存中就会出现两份System字节码。
试想一个场景:黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。
参考: 热修复入门:Android 中的 ClassLoader
简述双亲委托模型在热修复领域的应用
在用BaseDexClassLoader或者DexClassLoader去加载某个dex或者某个apk中的class时候,无法调用findClass()方法,因为该方法是包访问权限的,需要调用loadClass(),该方法其实是BaseDexClassLoader的父类ClassLoader实现的,findclass()方法就是双亲委托模型的实际体现。源码如下
public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, false);
}
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);//查找是否加载过该Class 文件
if (clazz == null) {//没有加载过,
try {
//调用父类的loadClass()方法去加载该文件
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
// Don't want to see this.
}
if (clazz == null) {//父类没有加载过或者加载失败,
clazz = findClass(className);//自己去加载
}
}
//说明加载过,直接返回
return clazz;
}
网友评论