最近一直在处理客户端动态加载、插件相关的功能,其中的核心便是ClassLoader 类加载器。
不看原理,先看现象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int i = 0;
ClassLoader classLoader = getClassLoader();
if (classLoader != null) {
LogUtil.e("classLoader " + i++ + " : " + classLoader.toString());
while (classLoader.getParent() != null) {
classLoader = classLoader.getParent();
LogUtil.e("classLoader " + i++ + " : " + classLoader.toString());
}
}
}
得到相关日志:
classLoader 0 : dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/XXX/base.apk"],nativeLibraryDirectories=[/data/app/XXX/lib/arm, /system/fake-libs, /data/app/XXX/base.apk!/lib/armeabi-v7a, /system/lib, /vendor/lib]]]
classLoader 1 : java.lang.BootClassLoader@81240c4
从日志中可以发现,有两个个ClassLoader实例,一个是 BootClassLoader(系统启动的时候创建的),另一个是 PathClassLoader(应用启动时创建的,用于加载 “/data/app/XXX/base.apk” 里面的类)。
在 Android 系统启动的时候会创建一个 Boot 类型的 ClassLoader 实例,用于加载一些系统 Framework 层级需要的类,我们的 Android 应用里也需要用到一些系统的类,所以 APP 启动的时候也会把这个 Boot 类型的 ClassLoader 传进来。此外,APP 也有自己的类,这些类保存在 APK 的 dex 文件里面,所以 APP 启动的时候,也会创建一个自己的 ClassLoader 实例,用于加载自己 dex 文件中的类。
所以, 一个运行的App至少拥有两个ClassLoader,那么这两个ClassLoader又是如果运作的呢?
查看Instrumentation源码,
public Activity newActivity(ClassLoader cl, String className,Intent intent)
throws InstantiationException, IllegalAccessException,ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
可以发现,在创建Activity实例的过程中,调用到的是ClassLoader的loadClass方法:
public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, false);
}
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
//先查询当前ClassLoader是否已经加载过
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
//再查询Parent是否加载过
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
//如果都没有加载过,再执行findClass方法,加载相关的class
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
所以一个类加载器的加载流程为:
1、先查询当前ClassLoader是否加载过
2、如果没有,再查询Parent是否加载过
3、如果继承路线上的Loader都没有加载过,则有Child执行加载工作
那Parent与当前Child又是什么关系呢?
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;
}
通过源码,可以发现,在创建一个构造器实例的时候,必须传入一个已存在的ClassLoader作为新创建的ClassLoader的ParentLoader;在刚开始的实验中,可以发现,调用PathClassLoader的getParent(),获取到的就是BootClassLoader。
这样,一个App中所有的ClassLoader都被关联起来了。