android 与 java 的类加载器
类别 | 加载文件 | 类加载器分类 |
---|---|---|
java | .class 文件 | {{java类加载机制}} |
android | .dex 文件 | {{android类加载机制}} |
java 类加载机制
-
BootStrapClassLoader:
启动类加载器
由 C/C++代码实现
加载 .../jre/lib 下类库 / -Xbootclasspath 参数指定的路径中的类库
顶级类加载器 -
ExtClassLoader
扩展类加载器
sun.misc.Launcher 中的内部类(即 sun.misc.Launcher$ExtClassLoader)
加载 .../jre/lib/ext 下的类库 / -Djava.ext.dirs 参数指定的路径中的类库
parent: 无,此时可以看做 parent 就是 BootStrapClassLoader -
AppClassLoader
应用程序类加载器
sun.misc.Launcher 中的内部类 (即 sun.misc.Launcher$AppClassLoader)
加载 classpath 所指定的类库
parent: ExtClassLoader -
自定义 classLoader
继承 ClassLoader
加载自行指定位置的类库
parent: AppClassLoader
- java 类加载机制采用双亲委派机制:
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//1.检查请求加载类是否已经加载
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//2.请求加载类还未加载,parent!=null,通过parent加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//3.parent == null,尝试采用BootStrapClassLoader启动类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//4.以上三步均未成功加载,通过本身加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
- 自定义 classLoader
//继承ClassLoader
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
//重写findClass(String name)方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = getByte(name);
//调用defineClass(name, data, 0, data.length)返回class
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
//获取流
private byte[] getByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(name);
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
}
Android 类加载器机制
注: 除了 classLoader 与 BootClassLoader,可以通过 as 直接查看,其他均无法直接查看,源码取自互联网
-
BootClassLoader
java.lang.ClassLoader 中的内部类(即 java.lang.ClassLoader$BootClassLoader)
预加载常用类 -
PathClassLoader
dalvik.system.PathClassLoader
加载已经安装的 Apk(/data/app/package)的 Apk 文件
只有构造方法
package dalvik.system;
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);
}
}
- DexClassLoader
dalvik.system.DexClassLoader
加载任意位置的 dex/zip/apk/jar
只有构造方法
package dalvik.system;
import java.io.File;
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
- BaseDexClassLoader
PathClassLoader/DexClassLoader 均继承于 BaseDexClassLoader
- PathClassLoader 与 DexClassLoader 的区别
只有构造函数所传参数不同
DexClassLoader 比 PathClassLoader 多传 optimizedDirectory: dex 优化缓存路径
optimizedDirectory : 用于存放:加载 jar/apk/zip 等压缩格式的程序文件时解压出其中的 dex 文件,如果本身只是 dex 文件,也会进行复制到此文件中
必须是需要加载的程序目录:即 data/data/packname/xxx
- android 中的 java.lang.ClassLoader 与 java 中的 java.lang.ClassLoader 并不相同 *
//可以看到android是无法通过defineClass来获取class类的
protected final Class<?> defineClass(String name, byte[] b, int off, int len)throws ClassFormatError{
throw new UnsupportedOperationException("can't load this type of class file");
}
- DexClassLoader 类加载流程
- BaseDexClassLoader
private final DexPathList pathList;
/**
* 首先执行ClassLoader构造方法
* 之后执行PathClassLoader/DexClassLoader的构造方法
* 创建DexPathList对象
*/
public class BaseDexClassLoader extends ClassLoader {
...
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent){
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
...
}
//findclass是交由构造方法中所创建的DexPathList对象来执行的
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
// 实质是通过pathList的对象findClass()方法来获取class
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
- DexPathList
private final Element[] dexElements;
/**
* 创建Element[]数组
* dexPath是要加载的dex/zip/jar/apk的原始路径,支持多个以:分割
* 遍历存入优化缓存路径当中
* 包装成Element对象,以Element[]数组的形式返回
*/
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
...
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
...
}
/**
* 遍历Element[]数组
* 找到name所对应的DexFile
* 由DexFile加载dex文件,值得注意的是,加载成功一个就会直接返回,后面有相同name的也不会加载了
* 通过类加载机制实现的热修复就是采用这个机制,将修复类的dex放在Element[]数组的最前方,则只会加载修复类的dex
* 而不会去加载本身所具有的dex即具有bug的dex
*/
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;
}
基于 android 类加载机制实现的热修复
从以上的分析可以得知:
- DexClassLoader 创建
构造方法传入希望加载的任意路径的 dex/zip/jar/apk 路径,以:分割
构造方法传入优化缓存路径,需 data/data/packageName/xxx
-
BaseDexClassLoader
构造方法创建 DexPathList 对象
DexPathList 对象构造方法获取 Element[]数组
获取 Element[]数组的过程是将 dex 文件包装成 Element 对象,并将任意位置的 dex/zip/jar/apk 存储到指定的优化缓存路径当中 -
DexClassLoader 继承于 BaseDexClassLoader
DexClassLoader 并没有重写 BaseDexClassLoader 的 findClass()方法
DexClassLoader 调用 findClass()
BaseDexClassLoader 调用 findClass()
DexPathList 调用 findClass()
遍历 Element[]数组,根据所传入 name 来加载对应 dex 文件
找到第一个之后,则直接加载并返回,后续的不再继续进行查找 -
基于 android 类加载机制实现的热修复
任意位置存入修复后的 dex/zip/jar/apk,设置优化缓存路径
创建 DexClassLoader
构造方法将任意位置存入的修复后的 dex/zip/jar/apk 存入设置的优化缓存路径当中,并包装成 Element[]数组
此时,我们未进行 findClass(), 修复后的 dex 文件并没有被类加载器加载
最理想的情况是,在第一次调用出现 bug 之前,findClass()进行修复后的 dex 文件加载替换掉有 bug 的 dex
我们也不可能去判断哪个时候是第一次加载,然后在其之前调用 DexClassLoader 中的 findClass()来加载,因为当我们上一次发布,即具有 bug 的程序时,是根本不知道这个 bug 的存在的
那么,换一个角度,当通过类加载器加载我们普通的 apk 中的类的时候,是通过 PathClassLoader 来完成的,那么,只需要修改 PathClassLoader 当中的 Element[]数组即可
将通过 DexClassLoader 生成的 Element[]数组,放置于 PathClassLoader 的 Element[]之前
那么,第一次加载 bug 的类的时候,系统通过 PathClassLoader 进行 findClass(),而此时在最前面的是我们在 DexClassLoader 中生成的 Element[]数组,这样,就不会再加载原有的具有 bug 的类了,也就是完成了修复
示例:
github示例
- 注意:
当已经通过类加载器加载了具有 bug 的类之后,修复类则不会加载了,所以我们将修复放在尽可能前一些的位置,这也是为什么我们常说的热修复需要重启的原因,可能在我们的修复包发送之前,客户已经启动过我们的应用了,也已经加载过 bug 类,他一直没有退过,则永远不会加载修复类
就像示例中所展示的,如果先点击了 add,则已经加载过 test 类,再点击就没有作用了
这时候,只需要把程序退出,然后重新启动,先点击修复,再点击 add,则可以正确执行
网友评论