Android classloader
Android的ClassLoader和Java的ClassLoader有一些差异:
- Java中的ClassLoader可以加载jar文件和Class文件(本质都是加载Class文件)
- Android中ClassLoader加载的不再是Class文件,而是dex文件(DVM,ART都是如此)
同Java ClassLoader类似,也分为系统ClassLoader和自定义ClassLoader
系统ClassLoader包括
PathClassLoader
应用启动时创建,会加载data/app/目录下的dex文件。App安装到手机后,apk里面的class.dex中的class均是通过 PathClassLoader来加载的。该类继承自BaseDexClassLoader,其本身除了两个构造函数之外没有其他逻辑。
DexClassLoader
可以加载dex文件以及包含dex的apk文件或jar文件 也支持从SD卡进行加载,即可以在应用未安装的情况下加载dex,其中也只有一个构造函数
BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
// dexPath要加载的dex文件所在的路径,optimizedDirectory是odex将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;
}
}
BaseClassLoader中最核心的的逻辑就是上面这些,可以看到findClass的逻辑是封装在DexPathList中的,BaseClassLoader中只是调用了下DexPathList的findClass方法。
DexPathList的功能是:
它的构造函数
/**
* dexPath: /storage/emulated/0/testdex.jar
* optimizedDirectory: /data/data/demo.ccccchen.com.dexdemo/app_temp
* parent classloader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/demo.ccccchen.com.dexdemo-2/base.apk"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]]
*/
public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory){
...
}
BaseDexClassLoader在创建DexPathList时,传入的optimizedDirectory直接传了一个null进来,关于这个字段的作用后续再关注。
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);
}
上面是DexPathList的构造函数,最重要的一句是makeDexElements(splitDexPath(dexPath)..),这里是将dexPath中的文件遍历一遍,如果是apk、dex或者jar文件,则将这些文件都加入到dexElements数组中。dexElements存储着所有的dex、apk、jar包文件
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private Element[] dexElements;
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;
}
而makeDexElements的源码是这样子的
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if the
* zip file turns out to be resource-only (that is, no classes.dex file in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
} else if (file.isDirectory()) {
// We support directories for looking up resources.
// This is only useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else {
System.logW("Unknown file type for: " + 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对象
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
DexFile中的loadClassBinaryName中最终解析出Class的代码是调用的native代码
private static native Class defineClassNative(String name, ClassLoader loader, int cookie)
throws ClassNotFoundException, NoClassDefFoundError;
总结
这些类中,最重要的一个元素就是这个dexElements,额外的dex、apk、jar包,只要以正确的方式被加入到这个数组中,那么,其中的类就都可以找到了。