@946898963
2020-07-12T17:14:29.000000Z
字数 4309
阅读 1087
Android源码分析
MultiDex的源码中使用到了BaseDexClassLoader中的相关方法,所以特地学习了下BaseDexClassLoader源码。
本文并非原创,转载自:Android BaseDexClassLoader源码阅读。
Java的类加载使用父类加载机制,Android开发采用的同样是Java语言,不过它并没有采用JVM实现的ClassLoader类,Android内部使用的是BaseDexClassLoader、PathClassLoader、DexClassLoader三个类加载器实现从DEX文件中读取类数据,其中PathClassLoader和DexClassLoader都是继承自BaseDexClassLoader实现,这里就分析一下它的实现代码。
关于DexClassLoader和PathClassLoader的区别,建议阅读:DexClassLoader和PathClassLoader的区别。
从前面的Java类委托机制直到ClassLoader的findClass方法就是在父类加载器无法加载的时候自己加载类的实现,在findClass方法里会将加载类的工作委托给DexPathList类实现。
public class BaseDexClassLoader extends ClassLoader {
// 需要加载的dex列表
private final DexPathList pathList;
// dexPath要加载的dex文件所在的路径,
// optimizedDirectory是dexopt将dexPath处dex优化后输出到的路径,这个路径必须是手机内部路劲,
// libraryPath是需要加载的C/C++库路径
// parent是父类加载器对象
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
// 使用pathList对象查找name类
Class c = pathList.findClass(name, suppressedExceptions);
return c;
}
}
接着查看DexPathList的实现代码,在内部会根据类加载器加载路径查找dex文件,然后将它们解析成Element对象,Element对象代表的时dex文件或资源文件,它里面保存了文件对象。
// Element类代表dex文件或资源文件的路径元素
static class Element {
private final File file;
private final boolean isDirectory;
private final File zip;
private final DexFile dexFile;
private ZipFile zipFile;
private boolean initialized;
// file文件,是否是目录,zip文件通常都是apk或jar文件,dexFile就是.dex文件
public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
this.file = file;
this.isDirectory = isDirectory;
this.zip = zip;
this.dexFile = dexFile;
}
}
在DexPathList类构造的时候会首先将dexPath变量内容分隔成多个文件路径,并且根据路径查找Android中的dex和资源文件,将它们解析后存放到Element数组中。
final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private final ClassLoader definingContext;
//
private final Element[] dexElements;
// 本地库目录
private final File[] nativeLibraryDirectories;
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
// 当前类加载器
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// 根据输入的dexPath创建dex元素对象
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
}
makeDexElements就是把前面dexPath里面解析到的路径下的文件全部遍历一遍,如果是dex文件或apk和jar文件就会查找它们内部的dex文件,将所有这些dex文件都加入到Element数组中,完成加载路径下面的所有dex解析。
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
// 所有从dexPath找到的文件
for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();
// 如果是文件夹,就直接将路径添加到Element中
if (file.isDirectory()) {
elements.add(new Element(file, true, null, null));
} else if (file.isFile()){
// 如果是文件且文件名以.dex结束
if (name.endsWith(DEX_SUFFIX)) {
try {
// 直接从.dex文件生成DexFile对象
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else {
zip = file;
try {
// 从APK/JAR文件中读取dex文件
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
suppressedExceptions.add(suppressed);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
在findClass方法里查找名称为name的类时只需要遍历Element数组找是dexFile就直接调用DexFile.loadClassBinaryName方法,这个方法能够从dex文件数据中生成Class对象。
// 加载名字为name的class对象
public Class findClass(String name, List<Throwable> suppressed) {
// 遍历从dexPath查询到的dex和资源Element
for (Element element : dexElements) {
DexFile dex = element.dexFile;
// 如果当前的Element是dex文件元素
if (dex != null) {
// 使用DexFile.loadClassBinaryName加载类
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
BaseDexClassLoader在构造的时候会先将dexPath使用“:”分隔开,然后遍历每个路径下面的所有文件,查找到.dex文件.apk.jar类型的文件并将它们保存在Element数组中,当程序需要加载类的时候会直接遍历所有的Element对象,查找到和dex文件相关的Element就直接加载数据生成Class对象。