ClassLoader 类图:
图片来自文末参考文章中.jpgAndroid中ClassLoader的介绍
-
ClassLoader
介绍:
ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。包括类加载,验证,卸载等
构造方法:
public abstract class ClassLoader { private ClassLoader parent; protected ClassLoader() { this(getSystemClassLoader(), false); } protected ClassLoader(ClassLoader parentLoader) { this(parentLoader, false); } ClassLoader(ClassLoader parentLoader, boolean nullAllowed) { if (parentLoader == null && !nullAllowed) { //父类的类加载器为空,则抛出异常 throw new NullPointerException("parentLoader == null && !nullAllowed"); } parent = parentLoader; } }
-
BootClassLoader
介绍:
-
父类构造器
-
BootClassLoader是ClassLoader的内部类,是单例类,包内可见,我们没法调用,也不能使用它来动态加载。Android系统启动时会使用BootClassLoader来预加载常用类。
构造方法:
class BootClassLoader extends ClassLoader { private static BootClassLoader instance; public static synchronized BootClassLoader getInstance() { if (instance == null) { instance = new BootClassLoader(); } return instance; } public BootClassLoader() { super(null, true); } }
-
-
BaseDexClassLoader
介绍:
-
BaseDexClassLoader继承自ClassLoader,是抽象类ClassLoader的具体实现类
-
PathClassLoader和DexClassLoader都继承它
-
在它内部会初始化DexPathList对象,DexPathList这个对象中存储了dexElements(记录所有的dexFile文件)和nativeLibraryPathElements(记录所有的Native动态库, 包括app目录的native库和系统目录的native库)
构造方法:
-
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}
参数:
-
dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用:分割;
-
optimizedDirectory: 优化后的dex文件存在的目录, 可以为null;
-
libraryPath: native库所在路径列表;当有多个路径则采用:分割;
-
ClassLoader:父类的类加载器.
-
PathClassLoader
介绍:
-
继承于BaseDexClassLoader. 只是封装了一下构造函数,没有复写父类的任何方法,所以具体的实现都在BaseDexClassLoader中
-
主要用于系统和app的类加载器,其中optimizedDirectory为null, 采用默认目录/data/dalvik-cache/
-
dexPath 比较受限制,一般是已经安装应用的apk文件路径。在Android中,App安装到手机后,apk里面的classes.dex中的class均是通过PathClassLoader来加载的
-
BootClassLoader 是 PathClassLoader 的父加载器
构造方法:
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
介绍:
-
封装了BaseDexClassLoader对象,并没有覆写父类的任何方法
-
可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载
-
构造方法中,参数optimizedDirectory用来缓存优化的dex文件的路径,即从apk或jar文件中提取出来的 dex 文件。该路径不可以为空,且应该是应用私有的,有读写权限的路径
构造方法:
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
双亲委托机制
类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。
具体委托过程如下:
-
源 ClassLoader 先判断该 Class 是否已加载,如果已加载,则直接返回 Class,如果没有则委托给父类加载器。
-
父类加载器判断是否加载过该 Class,如果已加载,则直接返回 Class,如果没有则委托给祖父类加载器。
-
依此类推,直到始祖类加载器(引用类加载器)。
-
始祖类加载器判断是否加载过该 Class,如果已加载,则直接返回 Class,如果没有则尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,则委托给始祖类加载器的子类加载器。
-
始祖类加载器的子类加载器尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,则委托给始祖类加载器的孙类加载器。
-
依此类推,直到源 ClassLoader。
-
源 ClassLoader 尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,源 ClassLoader 不会再委托其子类加载器,而是抛出异常。
类加载
-
loadClass(className) 调用加载class的方法。
在loadClass()内部会先判断是否已经加载过该class,如果没有加载过就调用父类的loadClass(),如果父类中没有加载到就调用自己的findClass(className)方法去自己的路径中加载该class
-
findClass(className)
-
findClass()方法内部中是调用pathList(pathList是在BaseDexClassLoader构造方法中初始化的DexPathList对象)的findClass(name...)
-
即调用DexPathList内部的findClass(name) 。在该方法中会遍历dexEldments中的dex文件,然后调用dex.loadClassBinaryName方法去查找该类。
-
即调用DexFile内部的,loadClassBinaryName方法,在该方法中调用defineClass()方法
-
defineClassNative()这是native方法,在该native方法中,去找对应的类
-
从源码分析 Android dexClassLoader 加载机制原理
ClassLoader在热修复中的应用
在前面类加载过程中分析说了,类加载机制是双亲委托机制,而且在loadClass过程中会遍历pathList(dexElements)中的dex文件,所以当在前面的dex文件中找到类就直接返回了,而不会再进行查找,所以可以通过将修复的dex插到dexElements前面即可。思路是这样的,具体的操作会复杂的多。
ClassLoader动态加载Jar实践
-
生成jar包my.jar
package com.thc.myjar; public class Plugin { public String getPluginStr() { return "恭喜你拿到了插件中的内容"; } public int addInPulgin(int a, int b) { return a + b; } }
-
使用dx.jar将jar包转换成dex化之后的jar包
dx --dex --output=mydex.jar my.jar
-
push到或者代码复制到sdcard目录,然后通过DexClassLoader来进行加载
private void loadJar() {
/**
* dexPath : dexPath
*
*/
String absolutePath = getCacheDir().getAbsolutePath();
DexClassLoader dexClassLoader = new DexClassLoader(
"/data/data/com.thc.pluginsample/cache/" + jarName,
absolutePath,
null,
getClassLoader());
try {
Class<?> aClass = dexClassLoader.loadClass("com.thc.myjar.Plugin");
Object o = aClass.newInstance();
Method getPluginStr = aClass.getMethod("getPluginStr");
String msg = (String) getPluginStr.invoke(o);
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
感谢巨人的肩膀:
网友评论