最近在研究插件動態(tài)加載,記錄一下其中ClassLoader
部分的內(nèi)容.
Android中的ClassLoader
與Java有些不同迅箩,Android中ClassLoader
加載的是dex文件塌忽,而Java中加載的是jar文件.相同的是兩者都采用了雙親委派模型.ClassLoader
中loadClass
函數(shù)體現(xiàn)了這個模型:
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);// 首先檢查Class是否已經(jīng)加載過
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);// 如果加載不成功仆救,調(diào)用findClass函數(shù)來獲取class對象.
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
其中findClass()
為protected拯杠,直接拋出異常翅萤,需要子類來實(shí)現(xiàn)具體的find過程.Android中ClassLoader
的子類是BaseDexClassLoder
,同時(shí)有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
都直接調(diào)用BaseDexClassLoader
的構(gòu)造函數(shù)其骄,區(qū)別是傳入的參數(shù)不一樣河泳,PathClassLoader
的super調(diào)用中,傳入的optimizedDirectory
一直為null.BaseDexClassLoder
的構(gòu)造函數(shù)使用入?yún)⒌娜齻€路徑構(gòu)造了一個DexPathList
對象.同時(shí)findClass()
直接使用DexPathList
的findClass()
函數(shù).看一下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
的構(gòu)造函數(shù)將傳入的路徑轉(zhuǎn)化為Element
數(shù)組年栓,findClass()
中遍歷Element
中的DexFile
來加載Class
拆挥,看一下Element
數(shù)組的生成過程:
/**
* 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);//構(gòu)造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)) {//如果同時(shí)有zip和dex文件纸兔,就構(gòu)造對應(yīng)的Element
elements.add(new Element(file, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
調(diào)用了兩次loadDexFile()
來生成dex對象,第一次的file為dex文件否副,第二次的file是壓縮文件(zip,jar,apk).看一下對應(yīng)的代碼:
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);
}
}
根據(jù)optimizedDirectory
是否為null
調(diào)用不同的方法來構(gòu)造DexFile.這兩種構(gòu)造方法最后都會調(diào)用openDexFile()
這個native函數(shù).其中outputName
就是optimizedDirectory
.回到前面看汉矿,PathClassLoader
和DexClassLoader
的區(qū)別是optimizedDirectory
是否為null
,對應(yīng)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
目錄中進(jìn)行加載。