在Android系统中,应用程序进程都是由Zygote进程孵化出来的,而Zygote进程是由Init进程启动的。Zygote进程在启动时会创建一个Dalvik虚拟机实例,每当它孵化一个新的应用程序进程时,都会将这个Dalvik虚拟机实例复制到新的应用程序进程里面去,从而使得每一个应用程序进程都有一个独立的Dalvik虚拟机实例。

每个android应用程序都是运行在虚拟机实例上的,而其中dex的加载就比如离开不了虚拟机的初始化。

JNI_CreateJavaVM

位于/dalvik/vm/Jni.cpp.
创建了一个虚拟机之后调用 dvmStartup 初始化虚拟机。

1
std::string status = dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);

dvmStartup

位于/dalvik/vm/Init.cpp)

1
2
3
4
5
6
7
8
9
10
11
12
13
std::string dvmStartup(int argc, const char* const argv[],
bool ignoreUnrecognized, JNIEnv* pEnv)
{
...
if (!dvmClassStartup()) { //初始化文件的结构体引用
return "dvmClassStartup failed";
}
...
if (!dvmFindRequiredClassesAndMembers()) { //初始化成员、类的引用
return "dvmFindRequiredClassesAndMembers failed";
}
...
}

dvmClassStartup

位于/dalvik/vm/oo/Class.cpp
处理启动类路径,打开特定的dex/jar文件,可能会进行一些优化。

1
2
3
4
5
6
bool dvmClassStartup()
{
...
processClassPath(gDvm.bootClassPathStr, true);
...
}

processClassPath

将一些文件路径转化为ClassPathEntry结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static ClassPathEntry* processClassPath(const char* pathStr, bool isBootstrap)
{
...
cp = mangle;
idx = 0;
while (cp < end) { //循环遍历pathStr
...
ClassPathEntry tmp;
tmp.kind = kCpeUnknown;
tmp.fileName = strdup(cp);
tmp.ptr = NULL;
cpe[idx].kind = kCpeLastEntry;
cpe[idx].fileName = NULL;
cpe[idx].ptr = NULL;
//为每个文件准备一个cpe
if (!prepareCpe(&tmp, isBootstrap)) {
/* drop from list and continue on */
free(tmp.fileName);
} else {
/* copy over, pointers and all */
cpe[idx] = tmp;
idx++;
}
}
cp += strlen(cp) +1;
}
...
return cpe;
}

prepareCpe

位于/dalvik/vm/oo/Class.cpp.
根据传进的文件名判断这是一个什么样的文件,提取出有用的信息,存入ClassPathEntry结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static bool prepareCpe(ClassPathEntry* cpe, bool isBootstrap)
{
...
char suffix[10];
getFileNameSuffix(cpe->fileName, suffix, sizeof(suffix));
//根据后缀名判断文件类型
if ((strcmp(suffix, "jar") == 0) || (strcmp(suffix, "zip") == 0) ||
(strcmp(suffix, "apk") == 0)) {
JarFile* pJarFile = NULL;
if (dvmJarFileOpen(cpe->fileName, NULL, &pJarFile, isBootstrap) == 0) {
cpe->kind = kCpeJar;
cpe->ptr = pJarFile;
return true;
}
} else if (strcmp(suffix, "dex") == 0) {
RawDexFile* pRawDexFile = NULL;
if (dvmRawDexFileOpen(cpe->fileName, NULL, &pRawDexFile, isBootstrap) == 0) {
cpe->kind = kCpeDex;
cpe->ptr = pRawDexFile;
return true;
}
} else {
ALOGE("Unknown type suffix '%s'", suffix);
}
...
}

dvmRawDexFileOpen

位于/dalvik/vm/RawDexFile.cpp.
打开一个原生dex文件,优化,加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,
RawDexFile** ppRawDexFile, bool isBootstrap)
{
...
dexFd = open(fileName, O_RDONLY); //打开文件
if (dexFd < 0) goto bail;
if (odexOutputName == NULL) { //构造一个dex cache name
cachedName = dexOptGenerateCacheFileName(fileName, NULL);
if (cachedName == NULL)
goto bail;
}
...
//打开cache中的文件,如果没有会新建一个,填充头部,验证dependencies
optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,
adler32, isBootstrap, &newFile, /*createIfMissing=*/true);
//新生成一个优化的dex
if (newFile) {
...
if (result) { //内部执行了system/bin目录下的dexopt文件
result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
fileName, modTime, adler32, isBootstrap);
}
...
}
if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {
ALOGI("Unable to map cached %s", fileName);
goto bail;
}
...
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));
(*ppRawDexFile)->cacheFileName = cachedName;
(*ppRawDexFile)->pDvmDex = pDvmDex; //新文件的指针
cachedName = NULL; // don't free it below
result = 0;
...
}


dvmFindRequiredClassesAndMembers

位于/dalvik/vm/InitRefs.cpp .
遍历类列表和成员列表,将他们的引用存入全局遍历gDvm中以便日后引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* Look up the set of classes and members used directly by the VM,
* storing references to them into the globals instance. See
* Globals.h. This function is exposed so that dex optimization may
* call it (while avoiding doing other unnecessary VM initialization).
*
* The function returns a success flag (true == success).
*/
bool dvmFindRequiredClassesAndMembers() {
/*
* Note: Under normal VM use, this is called by dvmStartup()
* in Init.c. For dex optimization, this is called as well, but in
* that case, the call is made from DexPrepare.c.
*/
return initClassReferences()
&& initFieldOffsets()
&& initConstructorReferences()
&& initDirectMethodReferences()
&& initVirtualMethodOffsets()
&& initFinalizerReference()
&& verifyStringOffsets();
}

仅以initClassReferences为例。

initClassReferences

位于/dalvik/vm/InitRefs.cpp .
初始化一下核心类、数组类、异常类的引用。

1
2
3
4
5
6
7
8
9
10
static bool initClassReferences() {
...
int i; //初始化类的引用
for (i = 0; classes[i].ref != NULL; i++) {
if (!initClassReference(classes[i].ref, classes[i].name)) {
return false;
}
}
return true;
}

initClassReference

位于/dalvik/vm/InitRefs.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static bool initClassReference(ClassObject** pClass, const char* name) {
ClassObject* result;
assert(*pClass == NULL);
if (name[0] == '[') { //基本类类型的数组
result = dvmFindArrayClass(name, NULL);
} else { //系统类的引用
result = dvmFindSystemClassNoInit(name);
}
if (result == NULL) {
ALOGE("Could not find essential class %s", name);
return false;
}
*pClass = result;
return true;
}

dvmFindSystemClassNoInit

位于/dalvik/vm/oo/Class.cpp.

1
2
3
4
5
6
7
8
9
10
/*
* Find the named class (by descriptor), searching for it in the
* bootclasspath.
*
* On failure, this returns NULL with an exception raised.
*/
ClassObject* dvmFindSystemClassNoInit(const char* descriptor)
{
return findClassNoInit(descriptor, NULL, NULL);
}

findClassNoInit

位于/dalvik/vm/oo/Class.cpp
通过文件描述符找到相应的类,如果类没有加载,我们就去加载并链接,但是不会执行.
这里会返回一个是通过加载得到的类还是用的已有的类定义的标识,以防在同一个class loader中被加载两次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static ClassObject* findClassNoInit(const char* descriptor, Object* loader,
DvmDex* pDvmDex)
{
//在已经加载的类列表中查找(hash)
clazz = dvmLookupClass(descriptor, loader, true);
if (clazz == NULL) { //如果没有找到相应的类切没有传进dex文件的参数,就在dex列表中查找,找到之后加载dex文件中的类
const DexClassDef* pClassDef;
...
if (pDvmDex == NULL) {
assert(loader == NULL); /* shouldn't be here otherwise */
pDvmDex = searchBootPathForClass(descriptor, &pClassDef); //在dex列表中查找
} else { //在提供的dex文件中查找
pClassDef = dexFindClass(pDvmDex->pDexFile, descriptor);
}
...
/* found a match, try to load it */
clazz = loadClassFromDex(pDvmDex, pClassDef, loader); //加载dex!
...
}
}

loadClassFromDex

位于/dalvik/vm/oo/Class.cpp.
从指定的dex文件中加载指定的类。此函数相当于loadClass()+defineClass().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* Try to load the indicated class from the specified DEX file.
*
* This is effectively loadClass()+defineClass() for a DexClassDef. The
* loading was largely done when we crunched through the DEX.
*
* Returns NULL on failure. If we locate the class but encounter an error
* while processing it, an appropriate exception is thrown.
*/
static ClassObject* loadClassFromDex(DvmDex* pDvmDex,
const DexClassDef* pClassDef, Object* classLoader)
{
...
pDexFile = pDvmDex->pDexFile;
//通过pClassDef得到class_data_item指针
pEncodedData = dexGetClassData(pDexFile, pClassDef);
if (pEncodedData != NULL) {
dexReadClassDataHeader(&pEncodedData, &header);
} else {
// Provide an all-zeroes header for the rest of the loading.
memset(&header, 0, sizeof(header));
}
//通过头部数据和class_data_item得到其他的数据
result = loadClassFromDex0(pDvmDex, pClassDef, &header, pEncodedData,
classLoader);
return result;
}

loadClassFromDex0

位于/dalvik/vm/oo/Class.cpp.
通过头部数据和class_data_item,返回一个class的全部数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static ClassObject* loadClassFromDex0(DvmDex* pDvmDex,
const DexClassDef* pClassDef, const DexClassDataHeader* pHeader,
const u1* pEncodedData, Object* classLoader)
{
ClassObject* newClass = NULL;
descriptor = dexGetClassDescriptor(pDexFile, pClassDef);
newClass->descriptor = descriptor; //描述符
SET_CLASS_FLAG(newClass, pClassDef->accessFlags); //权限标识
newClass->pDvmDex = pDvmDex;
newClass->primitiveType = PRIM_NOT;
newClass->status = CLASS_IDX;
...
newClass->super = (ClassObject*) pClassDef->superclassIdx; //父类
....
....
newClass->sourceFile = dexGetSourceFile(pDexFile, pClassDef); //源文件
/* caller must call dvmReleaseTrackedAlloc */
return newClass;
}

总结

虚拟机创建后开始初始化,其中一步就是初始化类的引用,类成员的引用。

在初始化文件结构体的引用时,虚拟机根据全局变量gDvm中的启动类路径来为每个类生成一个ClassPathEntry的结构体引用,处理的过程中为压缩文件/dex调用了不同的函数,
处理dex文件时顺便对齐进行了优化,生成了odex文件。(当然4.4之后由于采用了ART代替dalvik,优化后的文件格式应为elf)。

在初始化类中数据的引用时,先是在已经加载的类列表中查找,如果没有的话再逐个的对dex进行解剖查找,找到相应的dex后,加载这个dex文件解析全部数据。


Reference

Android在线源码
Dalvik虚拟机的启动过程分析
Dalvik的BOOTCLASSPATH和dexopt流程