ClassLoader的类型
Java中的ClassLoader加载的是 class 文件,但是Android中加载的是 dex 文件。在AndroidStudio中打开的ClassLoader.java,依次选择View > Tools Window > Hierarchy,可以调出继承关系图,如下图所示:

里面有很多个ClassLoader,其中几个比较重要的ClassLoader分别是:BootClassLoader、PathClassLoader和DexClassLoader。
- BootClassLoader,用于加载Android Framework层的代码。BootClassLoader定义在ClassLoader.java中,代码如下(本文源码基于Android SDK 30):
java
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
public BootClassLoader() {
super(null);
}
...
}
注意BootClassLoader类前面没有用public修饰,所以只有在同一个包中才能访问,在应用程序中是无法直接调用的。
- PathClassLoader,用于加载应用程序中的dex。应用的apk安装后会存储在系统的data/app/目录下,启动该应用时,系统会去data/app/目录下找到相应的apk加载到内存中,这个加载动作就是PathClassLoader完成的。PathClassLoader只能加载系统中已经安装的apk,对应到插件化技术中就是加载宿主apk。其代码如下:
java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
运行如下代码:
java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ClassLoader classLoader1 = getClassLoader();
Log.d("MainActivity", "classloader1:" + classLoader1);
ClassLoader classLoader2 = Activity.class.getClassLoader();
Log.d("MainActivity", "classloader2:" + classLoader2);
}
}
打印结果是,classLoader1是PathClassLoader;classLoader2是BootClassLoader。
- DexClassLoader,允许app在运行期间加载外部的dex,也可以加载包含dex的压缩文件(jar、zip、apk等),其代码如下:
java
public class DexClassLoader extends BaseDexClassLoader {
//从API 26开始,参数optimizedDirectory没有作用了
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
PathClassLoader与DexClassLoader有什么区别呢?从代码来看唯一的区别是DexClassLoader需要自己传optimizedDirectory(优化路径),PathClassLoader不需要。但是上面的代码中,DexClassLoader中调用父类的构造方法传的optimizedDirectory是null,事实上从API 26开始,参数optimizedDirectory已经废弃,没有作用了,所以从API 26开始,PathClassLoader与DexClassLoader没有区别了。
ClassLoader的继承关系
运行一个应用程序需要用到几种类型的类加载器呢?运行下面的代码:
scala
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader classLoader = MainActivity.class.getClassLoader();
while(classLoader != null){
Log.d("TAG", classLoader.toString());
classLoader = classLoader.getParent();
}
}
}
打印如下:
2024-04-02 20:45:28.247 3255-3255 TAG com.example.test D dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test-hYnsAnZzDkTxi8o73f_kmg==/base.apk"],nativeLibraryDirectories=[/data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test-hYnsAnZzDkTxi8o73f_kmg==/lib/x86, /data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test-hYnsAnZzDkTxi8o73f_kmg==/base.apk!/lib/x86, /system/lib, /system_ext/lib]]] 2024-04-02 20:45:28.248 3255-3255 TAG com.example.test D java.lang.BootClassLoader@85a3be0
可以看到两种类加载器,一种是PathClassLoader,另一种是BootClassLoader。DexPathList中包含了apk的路径,其中/data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test- hYnsAnZzDkTxi8o73f_kmg==/base.apk
就是应用安装在手机上的位置。DexPathList是在BaseDexClassLoader的构造方法中创建的,里面存储了dex相关文件的路径,在ClassLoader执行双亲委托机制进行查找流程时会从DexPathList中查找。
这几种常用的ClassLoader的继承关系如下:
ClassLoader的加载过程
Android的ClassLoader遵循双亲委托机制,所谓双亲委托机制是指加载dex文件的过程中,首先判断自己有没有加载过,如果没有,委托父加载器(注意这里父加载器是指ClassLoader.getParent()拿到的父加载器)去加载,依次递归,如果父加载器可以完成加载任务,就成功返回;只有父加载器无法完成此加载任务或者没有父加载器时,才自己去加载。
ClassLoader的加载方法为loadClass()方法,这个方法被定义在抽象类ClassLoader中,代码如下:
java
public abstract class ClassLoader {
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name); //注释1
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) { //注释2
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
private Class<?> findBootstrapClassOrNull(String name)
{
return null;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
其中注释1处检查传入的类是否已经加载过,如果已经加载过就直接返回该类。如果没有加载过,判断父加载器是否为null,如果不为null,调用父加载器的loadClass()方法;如果为null,调用findBootstrapClassOrNull()方法来加载,这个方法会直接返回null。如果注释2处代码成立,说明向上委托流程没有检查出类已经被加载,就会执行findClass()方法进行查找,ClassLoader中的findClass()方法直接抛出了异常,其子类BaseDexClassLoader对该方法进行了实现:
java
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
boolean isTrusted) {
super(parent);
...
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
...
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
...
// Check whether the class in question is present in the dexPath that
// this classloader operates on.
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions); //注释1
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
}
在BaseDexClassLoader的构造方法中新建了DexPathList,在注释1处调用了DexPathList的findClass()方法:
java
public final class DexPathList {
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
}
里面遍历Element数组dexElements,然后调用了Element的findClass()方法,Element是DexPathList的静态内部类:
java
public final class DexPathList {
/*package*/ static class Element {
private final File path;
private final DexFile dexFile;
private ClassPathURLStreamHandler urlHandler;
private boolean initialized;
public Element(DexFile dexFile, File dexZipPath) {
if (dexFile == null && dexZipPath == null) {
throw new NullPointerException("Either dexFile or path must be non-null");
}
this.dexFile = dexFile;
this.path = dexZipPath;
// Do any I/O in the constructor so we don't have to do it elsewhere, eg. toString().
this.pathIsDirectory = (path == null) ? null : path.isDirectory();
}
public Element(DexFile dexFile) {
this(dexFile, null);
}
public Element(File path) {
this(null, path);
}
...
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
}
}
从Element的构造函数可以看到,其内部封装了DexFile,DexFile是用来加载dex的,findClass()方法中调用了DexFile的loadClassBinaryName()方法:
java
public final class DexFile {
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
}
接着调用了defineClass()方法,里面调用了defineClassNative()方法,这个方法是Native方法,就不再往下分析了。