前言
Android有两个主要的classloader,分别是PathClassLoader、DexClassLoader。它们都继承自于BaseDexClassLoader。PathClassLoader主要是用来加载系统类和主Dex中的类,而DexClassLoader主要是用来加载其他Dex文件的类。
PathClassLoader
现在来看一下PathClassLoader的源码,如下所示:
/**
* Provides a simple {@link ClassLoader} implementation that operates on a list
* of files and directories in the local file system, but does not attempt to
* load classes from the network. Android uses this class for its system class
* loader and for its application class loader(s).
*/
public class PathClassLoader extends BaseDexClassLoader {
/**
* Creates a {@code PathClassLoader} that operates on a given list of files
* and directories. This method is equivalent to calling
* {@link #PathClassLoader(String, String, ClassLoader)} with a
* {@code null} value for the second argument (see description there).
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
/**
* Creates a {@code PathClassLoader} that operates on two given
* lists of files and directories. The entries of the first list
* should be one of the following:
*
*
* - JAR/ZIP/APK files, possibly containing a "classes.dex" file as
* well as arbitrary resources.
*
- Raw ".dex" files (not inside a zip file).
*
*
* The entries of the second list should be directories containing
* native library files.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
有两个构造函数,分别调用了父类BaseDexClassLoader的构造函数。
DexClassLoader
再看一下DexClassLoader的源码,如下所示:
/**
* A class loader that loads classes from {@code .jar} and {@code .apk} files
* containing a {@code classes.dex} entry. This can be used to execute code not
* installed as part of an application.
*
* This class loader requires an application-private, writable directory to
* cache optimized classes. Use {@code Context.getDir(String, int)} to create
* such a directory:
{@code
* File dexOutputDir = context.getDir("dex", 0);
* }
*
* Do not cache optimized classes on external storage.
* External storage does not provide access controls necessary to protect your
* application from code injection attacks.
*/
public class DexClassLoader extends BaseDexClassLoader {
/**
* Creates a {@code DexClassLoader} that finds interpreted and native
* code. Interpreted classes are found in a set of DEX files contained
* in Jar or APK files.
*
*
The path lists are separated using the character specified by the
* {@code path.separator} system property, which defaults to {@code :}.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; must not be {@code null}
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
可以看到构造函数还是调用了父类BaseDexClassLoader的构造函数。与PathClassLoader不同的是,第二个参数optimizedDirectory不为null。接下来我们重点分析BaseDexClassLoader。
BaseDexClassLoader
BaseDexClassLoader只有一个构造函数,如下:
/**
* Constructs an instance.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; may be {@code null}
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.originalPath = dexPath;
this.pathList =
new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
主要做的事情是生成了一个DexPathList类型的对象。我们进去DexPathList中看里面的逻辑。
/**
* Constructs an instance.
*
* @param definingContext the context in which any as-yet unresolved
* classes should be defined
* @param dexPath list of dex/resource path elements, separated by
* {@code File.pathSeparator}
* @param libraryPath list of native library directory path elements,
* separated by {@code File.pathSeparator}
* @param optimizedDirectory directory where optimized {@code .dex} files
* should be found and written to, or {@code null} to use the default
* system directory for same
*/
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
this.dexElements =
makeDexElements(splitDexPath(dexPath), optimizedDirectory);
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
DexPathList的构造函数主要调用makeDexElements方法为每一个Dex文件生成了一个Element对象,并将所有的Element对象存储在一个数组中:
private static Element[] makeDexElements(ArrayList files, File optimizedDirectory,
ArrayList suppressedExceptions) {
ArrayList elements = new ArrayList();
// 所有从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()]);
}
而makeDexElements方法中主要调用了loadDexFile生成DexFile对象,而loadDexFile的源码如下:
/**
* Constructs a {@code DexFile} instance, as appropriate depending
* on whether {@code optimizedDirectory} is {@code null}.
*/
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
而DexFile.loadDex方法最终还是会调用DexFile的构造函数,它们最终的区别是在调用openDexFile方法时传入的第二个参数outputName是否为null。
/*
* Open a DEX file. The value returned is a magic VM cookie. On
* failure, an IOException is thrown.
*/
native private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException;
那为什么PathClassLoader可以直接传参数null呢?因为我们的应用已经安装并优化了,优化后的dex存在于/data/dalvik-cache目录下,这是系统默认的文件夹。所以当openDexFile的第二个参数为null时,它会从该目录下找相对应的dex文件并将优化后的dex存放在/data/dalvik-cache目录下。而/data/dalvik-cache目录我们是没有访问权限的,所以PathClassLoader只能加载系统或者主Dex类的,而DexClassLoader可以加载其他类的Dex文件。
最后我们看一下DexPathList的findClass方法。
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
遍历上面说到的Element数组并从每一个目标Dex文件中查找目标类。找到后并返回。