类加载器
在Android中,类加载器(ClassLoader)是一个重要的组件,负责在运行时动态加载JVM和Android类库。Android的类加载器系统基于JVM的类加载器模型,但有一些特定的调整和优化,以适应Android平台的需要。
(1) Bootstrap ClassLoader:
- 这是最顶层的类加载器,由JVM实现。
- 主要加载Java和Android核心类库。
- 通常通过null作为父加载器。
(2) PathClassLoader(或DexClassLoader):
- Android特有的类加载器,用于从APK文件、DEX文件或JAR/ZIP文件中加载类。
- PathClassLoader是Android应用默认的类加载器,用于加载应用的类和资源。
DexClassLoader是PathClassLoader的一个子类,提供了从指定的路径加载DEX文件的能力,动态加载插件或模块化场景常用加载器。
//DexClassLoader.java
package dalvik.system;
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
//PathClassLoader
package dalvik.system;
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);
}
}
可以发现PathClassLoader和DexClassLoader源码很简单,只包含了一个构造函数,去调用父类BaseDexClassLoader(所有的工作都应该是在BaseDexClassLoader里完成的了)。而这两个加载器不同的是PathClassLoader的构造中少了optimizedDirectory这个参数,原因是PathClassLoader是加载/data/app中的apk,也就是系统中的apk,而这部分的apk都会解压释放dex到指定的目录中,这个操作由系统完成,不需要单独传入路径,而DexClassLoader传入,用来缓存需要加载的dex文件,并创建一个DexFile对象,如果为null,会直接使用dex文件原有路径创建DexFile(这个参数已经弃用,自API26起无效)。
(3) System ClassLoader(或AppClassLoader):
- Android系统的应用类加载器,继承自URLClassLoader。
- 用于加载Android系统的类和应用的类。
- 在Android中不直接引用System ClassLoader或AppClassLoader,通过ClassLoader.getSystemClassLoader()获取。
(4) 自定义ClassLoader:
- 可以继承ClassLoader类或其子类(如DexClassLoader)来创建自定义的类加载器。
- 自定义类加载器可以用于加载网络上的类、从数据库加载加密的类、或者实现更复杂的类加载逻辑。
类加载器的主要用途:
- 动态加载和执行代码,如插件化开发、热更新等。
- 加载和执行不同来源的代码,如从网络下载的JAR包或DEX文件。
- 隔离不同来源的代码,防止类冲突和安全问题。
注意:滥用类加载器可能导致内存泄漏和性能问题。在使用类加载器时,应该仔细考虑其生命周期和资源管理。
DexPathList
DexPathList是DexClassLoader和BaseDexClassLoader等类加载器用于处理DEX文件路径的一个内部类。当使用DexClassLoader或BaseDexClassLoader加载DEX文件时,DexPathList起到了关键的作用。
(1) 作用:DexPathList负责管理和维护DEX文件的路径信息,使类加载器能够正确地找到并加载DEX文件中的类。
(2) 构造:DexPathList在DexClassLoader或BaseDexClassLoader的构造函数中被创建。构造DexPathList时,需要提供DEX文件的路径、优化目录、库路径以及父类加载器等参数。
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
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;
}
}
(3) 成员变量:DexPathList有一个私有的final成员变量dexElements,是一个Element数组,包含了所有DEX文件的Element对象,每个Element对象对应一个DEX文件。
private final Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
...
this.definingContext = definingContext;
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
...
}
(4) 加载DEX文件:在DexPathList的构造函数中,会调用makeDexElements()方法来加载DEX文件。这个方法会遍历提供的DEX文件路径列表,并为每个DEX文件创建一个Element对象,然后将这些Element对象添加到dexElements数组中。
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {
// 1.创建Element集合
ArrayList<Element> elements = new ArrayList<Element>();
// 2.遍历所有dex文件(也可能是jar、apk或zip文件)
for (File file : files) {
ZipFile zip = null;
DexFile dex = null;
String name = file.getName();
...
// 如果是dex文件
if (name.endsWith(DEX_SUFFIX)) {
dex = loadDexFile(file, optimizedDirectory);
// 如果是apk、jar、zip文件(这部分在不同的Android版本中,处理方式有细微差别)
} else {
zip = file;
dex = loadDexFile(file, optimizedDirectory);
}
...
// 3.将dex文件或压缩文件包装成Element对象,并添加到Element集合中
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
// 4.将Element集合转成Element数组返回
return elements.toArray(new Element[elements.size()]);
}
(5) 加载类:当类加载器需要加载一个类时,会通过DexPathList的loadClass()方法来实现。这个方法会遍历dexElements数组中的每个Element对象,并尝试从对应的DEX文件中加载类。一旦找到需要加载的类,就会返回该类的Class对象。
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
// 遍历出一个dex文件
DexFile dex = element.dexFile;
if (dex != null) {
// 在dex文件中查找类名与name相同的类
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
(6) 优化:为了提高性能,DexPathList还支持DEX文件的优化。在加载DEX文件时,可以将DEX文件优化到指定的目录中,以减少内存占用和提高加载速度。