文章目录
1. Android 中有哪几种 ClassLoader?他们的作用和区别是什么?
2. 简述 ClassLoader 的双亲委托模型
3. 简述双亲委托模型在热修复领域的应用
首先了解下 ClassLoader 是什么
众所周知我们编写的 Java 程序编写的是 .java 文件,但是在运行运行前会将代码编译成 .class 文件,以 Android Studio 项目为例在一般在项目目录下的 /build/intermediates/classes/debug 文件夹中有很多小文件夹,点进去看这些都是 .class 文件,程序在运行时就是加载这些 .class 文件,那么负责加载这些 .class 文件的就是我们的 ClassLoaer。
一般在 Android 中程序打包会把多个 .class 文件打包成一个或多个 .dex 文件
1. Android 中有哪几种 ClassLoader?它们的作用和区别是什么?
我们先看一下Android源码有几个 ClassLoader
我们发现 ClassLoader 是个抽象类并且有两个子类 SecureClassLoader 、BaseDexClassLoader
SecureClassLoader 的子类是 URLClassLoader ,其只能用来加载 jar 文件,然而 jar 文件在 Android 的 Dalvik/ART 上没法使用的。
BaseDexClassLoader 的子类是 PathClassLoader 、DexClassLoader 和 InMemoryDexClassLoader ( API 26 添加 )。
PathClassLoader 还有一个子类是 DelegateLastClassLoader ( API 27 添加 )
如果 jar 文件中包含 .dex 文件也是可以使用 BaseDexClassLoader 加载的
那我们就分析一下 BaseDexClassLoader 的几个子类
PathClassLoader
PathClassLoader 是一个简单的 ClassLoader 实现、Android 使用此类作为其系统类加载器和其应用程序类加载器。也就是说 PathClassLoader 在应用启动时创建,只能加载已经安装到 Android 系统中的apk文件(/data/app目录下,解压为 dex 后优化为 odex)。
经过代码调用发现 PathClassLoader 的 parent 是 java.lang.BootClassLoader 这应该是最上层的 ClassLoader。
DelegateLastClassLoader
DelegateLastClassLoader 是一个实现先查找再委托的类加载器实现
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> cl = findLoadedClass(name);
if (cl != null) {
return cl;
}
try {
return Object.class.getClassLoader().loadClass(name);
} catch (ClassNotFoundException ignored) {
}
ClassNotFoundException fromSuper = null;
try {
return findClass(name);
} catch (ClassNotFoundException ex) {
fromSuper = ex;
}
try {
return getParent().loadClass(name);
} catch (ClassNotFoundException cnfe) {
throw fromSuper;
}
}
- 首先在当前加载器查找是否加载过这个类。
- 然后,尝试搜索此类的类加载器是否加载过这个类。
- 使用当前加载器尝试去加载类
- 最后委托给父加载器加载。
DexClassLoader
DexClassLoader 就不一样了他可以在可读取目录中加载 .dex 文件以及包含 .dex 的 .jar 、.zip 和 .apk 文件,比 PathClassLoader 更灵活,是实现热修复的关键。
这个加载器只有一个构造方法
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)
其中 optimizedDirectory 是用来缓存优化过的类或代码的, API 21 开始官方推荐放在context.getCodeCacheDir()
目录
InMemoryDexClassLoader
他的作用可以直接加载内存中的 dex 文件
2. 简述 ClassLoader 的双亲委托模型
这个问题我们从源码入手
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 首先查找类是否加载过,加载过则返回类
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//让父类查找并加载类
c = parent.loadClass(name, false);
} else {
//查找由引导类加载器加载的类,如果未找到,则返回 NULL
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
//
c = findClass(name);
}
}
return c;
}
(1)、首先查找当前类加载器是否加载过这个类,如果找到则返回这个类
(2)、如果找不到,就会调用父类的方法去查找是否加载过这个类,如果父类没有加载过就去查找祖父类加载器是否加载过,按照这样的逻辑一直查找到最上层的类加载器(始祖类加载器),因为始祖加载器没有 parent 了,就用引导类加载器去查找。
(3)、如果还没有找到就说明这个类 JVM 确实没有加载过,然后尝试使用引导类加载器加载这个类,如果成功则返回类,加载失败就再次尝试使用始祖类加载器,依次类推如果当前类加载器都加载失败则抛出异常
小结:这样做保证的类只会加载一次
3. 简述双亲委托模型在热修复领域的应用
热修复的原理就是要替换类文件,Android 的虚拟机( 基于寄存器 )与 Java 的虚拟机(基于栈)有些不同,它加载的不是 .class 字节码,而是 .dex 文件( 可以通过 Google 提供的 dx 工具对 .class 文件转换得到 ),所以上面提到的替换类文件就是等于替换 DEX 文件。
- 通过 PathClassLoader 来加载我们自身 App 的 dex 文件。
- 通过 DexClassLoader 来加载我们的没有 BUG 补丁dex 文件。
- 首先通过反射拿到分别两个 ClassLoader 的 < DexPathList pathList > ( 一个是我们自己应用的,另一个是我们补丁的 )
- 然后再次通过反射拿到分别两个 ClassLoader 中 pathList 里面的 <Element[] dexElements > 的值。
- 合并两个反射到的 Element 数组。( 需要把我们的补丁 dex 中的数组放在合并的数组最前面 )
- 将合并的新的数组,再次通过反射重新设置到我们自身 App 的 DexPathList 中,根据双亲委托模型类加载器会首先加载没有 BUG 的类