类加载---双亲委派机制
双亲委派机制指的是,当类加载器收到类加载的请求时候会首先调用父类(ClassLoader)的findLoadedClass方法,判断类是否加载过,如果已经加载过则直接返回,否则会将加载任务委托给成员变量parent,并不是指的父类。parent加载器在收到类加载请求后,也会先判断需要加载的类是否已经加载过,如果加载过则结束,否则也会将加载任务委托给成员变量parent去进行类加载。这里是一个循环过程,直到将家加载任务委托给Bootstrap ClassLoader 结束,如果Bootstrap ClassLoader也没有找到则交给各个子类自己加载,一直到最后,如果没有任何类加载器能加载则会抛出ClassNotFoundException。
ClassLoader
通过代码我们可以比较明确的了解到双亲委派的加载机制。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先判断需要加载的类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 如果parent 不为空则委托parent进行加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// parent 也没有找到则委托BootstrapClassLoader进行加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果以上都没有找到在此处抛出异常;
}
if (c == null) {
//如果依旧没有找到则调用自己的findClass继续查找
c = findClass(name);
}
}
//最终return 找到的结果
return c;
}
Android中的ClassLoader
接下来我们通过一个简单例子来看下android中各个类的加载都是由哪个类加载器进行加载的。
System.out.println(Activity.class.getClassLoader());
System.out.println(LoadUtils.class.getClassLoader());//自己写的任意类
System.out.println(String.class.getClassLoader());
System.out.println(LoadUtils.class.getClassLoader().getParent());
输出结果如下:
System.out: java.lang.BootClassLoader@7ed285f
System.out: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.plugin-2/base.apk", zip file "/sdcard/pluginapp-debug.apk"],nativeLibraryDirectories=[/data/app/com.example.plugin-2/lib/arm64, /system/lib64, /vendor/lib64]]]
System.out: java.lang.BootClassLoader@7ed285f
System.out: java.lang.BootClassLoader@7ed285f
可以看出String和Activity类是由BootClassLoader加载,而我们自己任意写的一个类则由
PathClassLoader进行加载。看到最后一条结果我们是否好奇为什么PathClassLoader的parent是BootClassLoader我们是在哪里给他赋值的呢,还有另外一个疑问为什么我们使用的是PathClassLoader加载Activity呢,这些都是从哪里来赋值的呢?
从ActivityThread的attach方法中一步步跟下来我们发现最终赋值调用的是ClassLoader中的createSystemClassLoader方法得到了上述打印结果的源头。
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
// TODO Make this a java.net.URLClassLoader once we have those?
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
接下来我们使用PathClassLoader和DexClassLoader来分别加载类。
String apkPath = "/sdcard/pluginapp-debug.apk";
long time1 = System.currentTimeMillis();
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, getCacheDir().getAbsolutePath(),null, LoadUtils.class.getClassLoader());
try {
Class<?> aClass = dexClassLoader.loadClass("com.example.pluginapp.PluginApp");
System.out.println(aClass.getName() + ", DexClassLoader cost time :" + (System.currentTimeMillis() - time1));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
long time2 = System.currentTimeMillis();
PathClassLoader pathClassLoader = new PathClassLoader(apkPath, dexClassLoader);
try {
Class<?> bClass = pathClassLoader.loadClass("com.example.pluginapp.PluginApp");
System.out.println(bClass.getName() + ", PathClassLoader cost time :" + (System.currentTimeMillis() - time2));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
打印结果如下
System.out: com.example.pluginapp.PluginApp, DexClassLoader cost time :23
System.out: com.example.pluginapp.PluginApp, PathClassLoader cost time :293
从上图可以看出其实DexClassLoader 和PathClassLoader 都可以加载外部的apk,从我多次实验来看dexclassLoade的加载时间会稍微短一些,所以加载外部apk我个人推荐使用DexClassLoader 。
DexClassLoader构造方法中parent传入PathClassLoader 简易流程图:
好处
1 避免重复加载类。
2 防止文中String.class 或者 Activity.class核心类被篡改。
写在最后
好记性不如烂笔头,学习一个知识需要自己手动的去进行代码编写加深印象,不要因为简单而不去实践,坚持把简单的事情做好,就是不简单!