涉及源碼(Android 4.4.2):
/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
/dalvik/vm/native/dalvik_system_DexFile.cpp
/dalvik/vm/RawDexFile.cpp
/dalvik/vm/JarFile.cpp
Java層
通過我們會通過創(chuàng)建一個DexClassLoader來加載我們的dex,下面就以此為切入點進行
dexClassLoader = new DexClassLoader(apkPath, getFilesDir().getAbsolutePath(), null, getClassLoader());
查看DexClassLoader的構(gòu)造方法。
/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
// dexPath:是加載apk/dex/jar的路徑
// optimizedDirectory:是優(yōu)化dex后得到的.odex文件的輸出路徑
// libraryPath:是加載的時候需要用到的so庫
// parent:給DexClassLoader指定父加載器
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
可以看到它調(diào)用的是父類的構(gòu)造函數(shù),所以直接來看BaseDexClassLoader的構(gòu)造函數(shù)。
/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
創(chuàng)建了一個DexPathList實例,下面來看看DexPathList的構(gòu)造函數(shù)。
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
private final Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
}
它調(diào)用的是makeDexElements方法來創(chuàng)建一個Element數(shù)組來存放Element對象,每個Element對象包含一個DexFile對象。
static class Element {
private final DexFile dexFile;
}
下面先看看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();
// 如果是一個dex文件
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);
}
// 如果是一個apk或者jar或者zip文件
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
zip = file;
try {
// 1、調(diào)用loadDexFile加載dex文件,得到一個DexFile對象
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
suppressedExceptions.add(suppressed);
}
} else if (file.isDirectory()) {
elements.add(new Element(file, true, null, null));
} else {
System.logW("Unknown file type for: " + file);
}
// 2、把DexFile對象封裝到Element對象中,然后將Element對象加入Element數(shù)組
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
重點看看loadDexFile加載dex文件
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方法,其實調(diào)用的也是創(chuàng)建一個DexFile對象,所以我們需要關(guān)注的是DexFile對象的創(chuàng)建方法。
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
return new DexFile(sourcePathName, outputPathName, flags);
}
/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
public DexFile(String fileName) throws IOException {
mCookie = openDexFile(fileName, null, 0);
mFileName = fileName;
guard.open("close");
}
可以看到,它調(diào)用openDexFile來進行dex的加載。
private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException {
return openDexFileNative(new File(sourceName).getCanonicalPath(),
(outputName == null) ? null : new File(outputName).getCanonicalPath(),
flags);
}
native private static int openDexFileNative(String sourceName, String outputName,
int flags) throws IOException;
最終調(diào)用的是一個openDexFileNative的native方法來進行加載,加載完成之后,會返回一個int值,它對應的是加載dex的指針。
整個過程如下圖所示:

C/C++層
下面進入從native層進行分析,openDexFileNative方法對應的native層方法就是dalvik_system_DexFile.cpp文件的Dalvik_dalvik_system_DexFile_openDexFileNative方法。
/dalvik/vm/native/dalvik_system_DexFile.cpp
static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
JValue* pResult)
{
StringObject* sourceNameObj = (StringObject*) args[0];
StringObject* outputNameObj = (StringObject*) args[1];
DexOrJar* pDexOrJar = NULL;
JarFile* pJarFile;
RawDexFile* pRawDexFile;
char* sourceName;
char* outputName;
sourceName = dvmCreateCstrFromString(sourceNameObj);
if (outputNameObj != NULL)
outputName = dvmCreateCstrFromString(outputNameObj);
else
outputName = NULL;
// 1、嘗試把它當做一個后綴為.dex的DEX文件進行打開,得到RawDexFile結(jié)構(gòu)數(shù)據(jù)
// 2、如果打開失敗,則把它當做一個包含有classes.dex文件的Zip文件進行打開,得到JarFile結(jié)構(gòu)數(shù)據(jù)
if (hasDexExtension(sourceName)
&& dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
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;
}
if (pDexOrJar != NULL) {
pDexOrJar->fileName = sourceName;
// 3、把pDexOrJar這個結(jié)構(gòu)體中的內(nèi)容加到gDvm中的userDexFile結(jié)構(gòu)的hash表中,以便dalvik以后的查找
addToDexFileTable(pDexOrJar);
} else {
free(sourceName);
}
free(outputName);
RETURN_PTR(pDexOrJar);
}
我們傳入的可以是dex文件也可以是一個apk文件,所以分兩種情況處理,但是處理方式基本一致。
1、后綴為.dex的DEX文件處理,得到RawDexFile結(jié)構(gòu)數(shù)據(jù)
/dalvik/vm/RawDexFile.cpp
/* See documentation comment in header. */
int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,
RawDexFile** ppRawDexFile, bool isBootstrap)
{
DvmDex* pDvmDex = NULL;
char* cachedName = NULL;
int result = -1;
int dexFd = -1;
int optFd = -1;
u4 modTime = 0;
u4 adler32 = 0;
size_t fileSize = 0;
bool newFile = false;
bool locked = false;
// 1、打開.dex文件
dexFd = open(fileName, O_RDONLY);
if (dexFd < 0) goto bail;
// 2、驗證dex版本信息,并且獲取adler32值存放到adler32里面
if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) {
ALOGE("Error with header for %s", fileName);
goto bail;
}
// 3、得到dex文件的修改時間和文件大小,分別保存在變量modTime和filesize中
if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) {
ALOGE("Error with stat for %s", fileName);
goto bail;
}
// 4、如果優(yōu)化dex后的輸出目錄為空,則會生成一個目錄,否則odexOutputName為輸出目錄
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName, NULL);
if (cachedName == NULL)
goto bail;
} else {
cachedName = strdup(odexOutputName);
}
// 5、調(diào)用函數(shù)dexOptCreateEmptyHeader,構(gòu)造了一個DexOptHeader結(jié)構(gòu)體,寫入fd并返回
optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,
adler32, isBootstrap, &newFile, /*createIfMissing=*/true);
locked = true;
/*
* If optFd points to a new file (because there was no cached
* version, or the cached version was stale), generate the
* optimized DEX. The file descriptor returned is still locked,
* and is positioned just past the optimization header.
*/
// 如果成功生成了opt頭
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen = dvmGetRelativeTimeUsec();
// 6、將dex文件中的內(nèi)容拷貝到當前odex文件,從dexOffset開始
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}
if (result) {
//7、對dex文件進行優(yōu)化,并將優(yōu)化數(shù)據(jù)寫入odex文件
result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
fileName, modTime, adler32, isBootstrap);
}
endWhen = dvmGetRelativeTimeUsec();
}
// 8、將odex文件數(shù)據(jù)轉(zhuǎn)換為pDvmDex結(jié)構(gòu)
if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {
ALOGI("Unable to map cached %s", fileName);
goto bail;
}
if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(optFd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}
ALOGV("Successfully opened '%s'", fileName);
//9、分配內(nèi)存,填充結(jié)構(gòu)體 RawDexFile
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));
(*ppRawDexFile)->cacheFileName = cachedName;
(*ppRawDexFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;
bail:
free(cachedName);
if (dexFd >= 0) {
close(dexFd);
}
if (optFd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(optFd);
close(optFd);
}
return result;
}
這種方式的處理思路如下圖所示:

2、包含有classes.dex文件的Zip文件打開方式,得到JarFile結(jié)構(gòu)數(shù)據(jù)
/dalvik/vm/JarFile.cpp
int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap)
{
ZipArchive archive;
DvmDex* pDvmDex = NULL;
char* cachedName = NULL;
bool archiveOpen = false;
bool locked = false;
int fd = -1;
int result = -1;
// 1、打開zip文件,存放在archive中
if (dexZipOpenArchive(fileName, &archive) != 0)
goto bail;
archiveOpen = true;
dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
if (fd >= 0) {
ALOGV("Using alternate file (odex) for %s ...", fileName);
if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
ALOGE("%s odex has stale dependencies", fileName);
free(cachedName);
cachedName = NULL;
close(fd);
fd = -1;
goto tryArchive;
} else {
ALOGV("%s odex has good dependencies", fileName);
}
} else {
ZipEntry entry;
tryArchive:
//2、從壓縮包里找到Dex文件,然后打開這個文件
entry = dexZipFindEntry(&archive, kDexInJarName);
if (entry != NULL) {
bool newFile = false;
// 如果優(yōu)化dex后的輸出目錄為空,則會生成一個目錄,否則odexOutputName為輸出目錄
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName,
kDexInJarName);
if (cachedName == NULL)
goto bail;
} else {
cachedName = strdup(odexOutputName);
}
// 3、調(diào)用函數(shù)dexOptCreateEmptyHeader,構(gòu)造了一個DexOptHeader結(jié)構(gòu)體,寫入fd并返回
fd = dvmOpenCachedDexFile(fileName, cachedName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap, &newFile, /*createIfMissing=*/true);
locked = true;
// 如果成功生成了opt頭
if (newFile) {
u8 startWhen, extractWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(fd, 0, SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen = dvmGetRelativeTimeUsec();
// 4、將dex文件中的內(nèi)容拷貝到當前odex文件
result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
extractWhen = dvmGetRelativeTimeUsec();
}
if (result) {
//5、對dex文件進行優(yōu)化,并將優(yōu)化數(shù)據(jù)寫入odex文件
result = dvmOptimizeDexFile(fd, dexOffset,
dexGetZipEntryUncompLen(&archive, entry),
fileName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap);
}
endWhen = dvmGetRelativeTimeUsec();
}
} else {
goto bail;
}
}
// 6、將odex文件數(shù)據(jù)轉(zhuǎn)換為pDvmDex結(jié)構(gòu)
if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
goto bail;
}
if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(fd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}
ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName);
// 7、分配內(nèi)存,填充結(jié)構(gòu)體 RawDexFile
*ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
(*ppJarFile)->archive = archive;
(*ppJarFile)->cacheFileName = cachedName;
(*ppJarFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;
bail:
if (archiveOpen && result != 0)
dexZipCloseArchive(&archive);
free(cachedName);
if (fd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(fd);
close(fd);
}
return result;
}
這種方式的處理思路如下圖所示:

從java層到native層整個過程圖如下:

參考文章:
http://bbs.pediy.com/thread-199230.htm
http://bbs.pediy.com/thread-197274.htm