上一篇文章讲到一个有意思的地方,见 谈谈Android P行为变更与内联优化 文章说到,在Andorid Q上加载org.apache.http.legacy.boot.jar和Android P有点不同,Android P上是插入到App的PathClassLoader的pathList中的dexElements完成加载的,而Android Q上加载org.apache.http.legacy.boot.jar的却是一个独立的PathClassLoader对象,但是和App的PathClassLoader属于不同的ClassLoader实例,奇怪的是其父类都是BootClassLoader,根据双亲委托机制,这个类查找显得有点逆天,所以扒了下Android Q的源码,发现是BaseDexClassLoader里做了一点改动。
Android Q的BaseDexClassLoader源码可以参考 BaseDexClassLoader.java
可以从源码中发现一点猫腻,Android Q中该类多了几个构造函数,并且多了一个十分重要的字段sharedLibraryLoaders,该字段是一个ClassLoader数组
1 | /** |
在findClass方法中,类查找都会先从sharedLibraryLoaders中进行查找,找不到再从自己的pathList中进行查找,其逻辑如下
1 | @Override |
那么sharedLibraryLoaders是从哪里来呢,其实它是来自其构造函数,该构造函数是hide的,因此我们是无法直接调用到的,因此在Android Q上我们new一个PathClassLoader后,如果使用new出来的实例查找org.apache.http.legacy.boot.jar中的类是无法找到的,这一点和Android P有所区别,Android P由于是将它插入到pathList的dexElements,所以在Android P上是可以找到的。
因为这个特性,在Android P上,使用非标准ClassLoader加载org.apache.http.legacy.boot.jar中的类,可能出现org.apache.http.legacy.boot.jar中的类被不同的ClassLoader加载,如果出现内联,就可能会出现问题。但是在Android Q上,只要我们不使用带sharedLibraryLoaders的构造函数,那么该字段就会是null,查找类就会找不到,只有App的PathClassLoader才能找到org.apache.http.legacy.boot.jar中的类,我们自己使用公共构造函数创建的PathClassLoader都将查找不到,完全可以杜绝内联abort风险。
BaseDexClassLoader的带sharedLibraryLoaders的构造函数和PathClassLoader的带sharedLibraryLoaders构造函数如下
1 | /** |
1 | /** |
系统创建App的PathClassLoader来自ClassLoaderFactory对象,见 ClassLoaderFactory.java
1 | /** |
而调用ClassLoaderFactory.createClassLoader的方法来自ApplicationLoaders类,源码见 ApplicationLoaders.java
如果使用了带sharedLibraries的PathClassLoader,那么使用的就是ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries()方法,其调用来源来自LoadedApk,源码见 LoadedApk.java,关键代码如下,用了一个递归操作将所有SharedClassLoader串了起来:
1 |
|
注意此处ClassLoader的parent对象传递的是null,这就解释了为什么用了独立的PathClassLoader,但是其父亲ClassLoader和App的PathClassLoader的父亲都是BootClassLoader的原因。
而List
1 | /** |
该字段也是Android Q中新增的一个字段,其数据结构可以参考SharedLibraryInfo.java。
根据以上源码分析就可以完全解释为什么在Android Q上与Android P上org.apache.http.legacy.boot.jar的加载现象不一致的问题,从代码中也可以看出,这个操作可以杜绝Andorid P上org.apache.http.legacy.boot.jar内联abort问题的发生。
