最近在研究插件动态加载,记录一下其中ClassLoader
部分的内容.
Android中的ClassLoader
与Java有些不同,Android中ClassLoader
加载的是dex文件,而Java中加载的是jar文件.相同的是两者都采用了双亲委派模型.ClassLoader
中loadClass
函数体现了这个模型:
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);// 首先检查Class是否已经加载过
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
clazz = parent.loadClass(className, false);//尝试使用parent ClassLoader去加载Class
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
clazz = findClass(className);// 如果加载不成功,调用findClass函数来获取class对象.
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
其中findClass()
为protected,直接抛出异常,需要子类来实现具体的find过程.Android中ClassLoader
的子类是BaseDexClassLoder
,同时有PathClassLoader
和DexClassLoder
两个类直接继承自BaseDexClassLoder
.
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
public class BaseDexClassLoader extends ClassLoader{
/**
* 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);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
//more code
}
DexClassLoader
和PathClassLoader
都直接调用BaseDexClassLoader
的构造函数,区别是传入的参数不一样,PathClassLoader
的super调用中,传入的optimizedDirectory
一直为null.BaseDexClassLoder
的构造函数使用入参的三个路径构造了一个DexPathList
对象.同时findClass()
直接使用DexPathList
的findClass()
函数.看一下DexPathList
的源码:
/*package*/ final class DexPathList {
/** list of dex/resource (class path) elements */
private final Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
// some error checking
this.definingContext = definingContext;
this.dexElements =
makeDexElements(splitDexPath(dexPath), optimizedDirectory);
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
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;
}
/*package*/ static class Element {
public final File file;
public final ZipFile zipFile;
public final DexFile dexFile;
}
}
DexPathList
的构造函数将传入的路径转化为Element
数组,findClass()
中遍历Element
中的DexFile
来加载Class
,看一下Element
数组的生成过程:
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makeDexElements(ArrayList<File> files,
File optimizedDirectory) {
ArrayList<Element> elements = new ArrayList<Element>();
for (File file : files) {
ZipFile zip = null;
DexFile dex = null;
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) { // 文件后缀为.dex
try {
dex = loadDexFile(file, optimizedDirectory);//1.加载dex文件
} 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)) {// 文件为压缩包
try {
zip = new ZipFile(file);//构造zip对象
} catch (IOException ex) {
System.logE("Unable to open zip file: " + file, ex);
}
try {
dex = loadDexFile(file, optimizedDirectory);//2.加载dex文件
} catch (IOException ignored) {
// 如果压缩文件中没有dex文件,抛出这个异常,可以直接无视
}
} else {
System.logW("Unknown file type for: " + file);
}
if ((zip != null) || (dex != null)) {//如果同时有zip和dex文件,就构造对应的Element
elements.add(new Element(file, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
调用了两次loadDexFile()
来生成dex对象,第一次的file为dex文件,第二次的file是压缩文件(zip,jar,apk).看一下对应的代码:
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);
}
}
根据optimizedDirectory
是否为null
调用不同的方法来构造DexFile.这两种构造方法最后都会调用openDexFile()
这个native函数.其中outputName
就是optimizedDirectory
.回到前面看,PathClassLoader
和DexClassLoader
的区别是optimizedDirectory
是否为null
,对应openDexFile()
中outputName
是否为null
.
// java code
native private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException;
// native code
static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args,
JValue* pResult)
{
// ....
if (hasDexExtension(sourceName)
&& dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
ALOGV("Opening DEX file '%s' (DEX)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
pDexOrJar->pDexMemory = NULL;
} else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
ALOGV("Opening DEX file '%s' (Jar)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
pDexOrJar->pDexMemory = NULL;
} else {
ALOGV("Unable to open DEX file '%s'", sourceName);
dvmThrowIOException("unable to open DEX file");
}
//....
}
加载dex文件的过程在dvmRawDexFileOpen()
和dvmJarFileOpen()
方法中完成.看一下dvmJarFileOpen()
的代码:
int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap)
{
tryArchive:
entry = dexZipFindEntry(&archive, kDexInJarName);
if (entry != NULL) {
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName,
kDexInJarName);
if (cachedName == NULL)
goto bail;
} else {
cachedName = strdup(odexOutputName);
}
}
bail:
/* clean up, closing the open file */
if (archiveOpen && result != 0)
dexZipCloseArchive(&archive);
free(cachedName);
if (fd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(fd);
close(fd);
}
return result;
}
在odexOutputName
为null
的情况下,会直接返回一个ERROR,表示加载失败。
所以PathClassLoader
只能加载dex格式的文件,而DexClassLoader
可以加载dex文件,对于apk等压缩文件,加载过程会将压缩文件中的class.dex解压到optimizedDirectory
目录中进行加载。