1.java classloader要点
1.1 类的加载流程
1.2 类的加载机制之双亲委派模型
双亲委派机制工作流程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。 因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
双亲委派机制的好处:
- 避免重复加载:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
- 安全:其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
2.android中的classloader分类
2.1 关系类图
2.2 五种类加载器
2.2.1 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);
}
}
- 继承自BaseDexClassLoader;
- PathClassLoader 只能加载/data/app中的apk,也就是已经安装到手机中的apk;
2.2.2 DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
- 继承自BaseDexClassLoader;
- DexClassLoader 可以加载任何路径的apk/dex/jar
2.2.3 BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList; //记录dex文件路径信息
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}
- PathClassLoader与DexClassLoader的公共父类。
- dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用“:”分割
- File optimizedDirectory:优化后dex文件存在的目录, 可以为nul。由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径。这也是对apk中dex根据平台进行ODEX优化的过程。其实APK是一个程序压缩包,里面包含dex文件,ODEX优化就是把包里面的执行程序提取出来,就变成ODEX文件。
- libraryPath: native库所在路径列表;当有多个路径则采用:分割;
2.2.4 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);
} }
- Android平台上所有ClassLoader的最终parent。
- ClassLoader的内部类,仅包内可见,所以我们没法使用。
2.2.5 URLClassLoader
- 只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器
2.2.6 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;
}
}
2.3 Context#getClassLoader与Classloader#getSystemClassLoader异同
首先,我们看下面一段代码,在Application的onCreate()中添加如下代码:
PathClassLoader classLoader = (PathClassLoader) getApplicationContext().getClassLoader();
Log.d("mytest", "classLoader : " + classLoader + "\n" +
"parent : " + classLoader.getParent() + "\n" +
"grandParent : " + classLoader.getParent().getParent() + "\n" +
"system classloader : " + ClassLoader.getSystemClassLoader() + "\n" +
"system parent : " + ClassLoader.getSystemClassLoader().getParent());
代码的执行结果,打印内容如下:
classLoader : dalvik.system.PathClassLoader[dexPath=/data/app/com.gavin.demo2application-1.apk,libraryPath=/data/app-lib/com.gavin.demo2application-1]
parent : java.lang.BootClassLoader@41099128
grandParent : null
system classloader : dalvik.system.PathClassLoader[dexPath=.,libraryPath=null]
system parent : java.lang.BootClassLoader@41099128
- Context#getClassLoader获取到的是以当前apk安装路径为dexpath的PathClassLoader,其父classloader为BootClassLoader
- Classloader.getSystemClassLoader获取到的是PathClassLoader,其父classloader为同一个 BootClassLoader
再做如下测试
Log.i("LiaBin", "Context的类加载加载器:" + Context.class.getClassLoader());//系统库的class,所以是BootClassLoader
Log.i("LiaBin", "String的类加载加载器:" + String.class.getClassLoader());//系统库的class,所以是BootClassLoader,不同于java吧,java应用程序的话打印的是null
Log.i("LiaBin", "MainActivity的类加载器:" + MainActivity.class.getClassLoader());//本地的class,所以是PathClassLoader
Log.i("LiaBin", "StringRequest的类加载器:" + StringRequest.class.getClassLoader());//第三方库class,所以也是PathClassLoader。。v7,v4,recycleview中的也是第三方库哟
Log.i("LiaBin", "应用程序默认加载器:" + getClassLoader()); //这才是默认的加载器DexPathList[[zip file "/data/app/demo.lbb.mytest-1.apk"]..
Log.i("LiaBin", "系统类加载器:" + ClassLoader.getSystemClassLoader()); //上面代码可以知道此时是PathClassLoader,同时DexPathList[[directory "."]..
代码执行结果如下:
I/LiaBin: Context的类加载加载器:java.lang.BootClassLoader@41cb45f0
I/LiaBin: String的类加载加载器:java.lang.BootClassLoader@41cb45f0
I/LiaBin: MainActivity的类加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]]
I/LiaBin: StringRequest的类加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]]
I/LiaBin: 应用程序默认加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]]
I/LiaBin: 系统类加载器:dalvik.system.PathClassLoader[DexPathList[[directory “.”],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
- Context#getClassLoader获取到的PathClassLoader是用来加载自定义类如MainActivity、StringRequest的应用程序默认类加载器。
- Classloader.getSystemClassLoader获取到的PathClassLoader是用来加载系统类如String,Context的系统类加载器。
3.android中classloader的初始化时机
classloader的初始化时机可以参考https://juejin.im/entry/5819992867f356005879f6de
- classloader保存在LoadedApk对象中,一般通过LoadedApk#getClassLoader方法获取;
- ActivityThread.handleBindApplication
==> Application app = data.info.makeApplication(data.restrictedBackupMode, null);
==> app = mActivityThread.mInstrumentation.newApplication( LoadedApk#getClassLoader(), appClass, appContext); - 四大组件启动时都经历了LoadedApk#getClassLoader获取classloader