当在虚拟机里加载Dex文件后,这个文件的数据已经读取到内存里,能不能马上使用呢?能不能使用里面的类呢?显然是不行的,因为那些加载到内存的数据,只是储存的格式,不具备运行的条件,因此需要调用方法defineClass来定义类,才可以运行在虚拟机里。所有Java编译后的类保存在Dex文件里,使用上面介绍的方法openDexFile打开Dex文件,接着需要调用方法defineClass来定义类,其实就是调用原生的函数Dalvik_dalvik_system_DexFile_defineClass, 类的加载流程如下:

1)方法openDexFile里通过dvmDexFileOpenFromFd函数调用dexFileParse函数,分析Dex文件里每个类名称和类的代码所在索引,然后dexFileParse调用函数dexParseOptData来把类名称写对象pDexFile->pClassLookup里面,当然也更新了索引。

2)Dalvik_dalvik_system_DexFile_defineClass函数调用dvmDefineClass函数。

3)dvmDefineClass函数调用findClassNoInit来进类的初始化。

4)findClassNoInit调用dexFindClass找到类名称在文件中位置。

5)findClassNoInit调用loadClassFromDex从文件加载。

6)loadClassFromDex调用dexGetClassData函数获取类的索引地址。

7)loadClassFromDex调用loadClassFromDex0获取类接口、类成员变量、类的方法,这样就完成类从Dex文件里加载。


下面来依次分析调用的函数代码,理解其中细节,以及相应的代码,以便更加深入,达到修改代码的水平,Dalvik_dalvik_system_DexFile_defineClass函数代码如下:

staticvoid Dalvik_dalvik_system_DexFile_defineClass(const u4* args,

JValue* pResult)

{

StringObject* nameObj = (StringObject*)args[0];

这行代码是获取输入需要加载的类对象。


Object* loader = (Object*) args[1];

这行代码是获取类加载器对象。


int cookie = args[2];

这行代码是获取打开的Dex文件对象。


Object* pd = (Object*) args[3];

这行代码是获取保护域权限。


ClassObject* clazz = NULL;

DexOrJar* pDexOrJar = (DexOrJar*) cookie;

这行代码转换为Dex文件对象。


DvmDex* pDvmDex;

char* name;

char* descriptor;


name = dvmCreateCstrFromString(nameObj);

descriptor = dvmDotToDescriptor(name);

LOGV("--- Explicit class load '%s'0x%08x\n", descriptor, cookie);

free(name);

这段代码是把类名称对象转换为C字符串,然后从C字符串转换为类描述符。



if (!validateCookie(cookie))

RETURN_VOID();

这段代码是检查是否合法的Dex文件对象。



if (pDexOrJar->isDex)

pDvmDex =dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);

else

pDvmDex =dvmGetJarFileDex(pDexOrJar->pJarFile);

这段代码是获取Dex文件对象,以便从Dex文件里获取到类数据。



/* once we load something, we can't unmapthe storage */

pDexOrJar->okayToFree = false;


clazz = dvmDefineClass(pDvmDex, descriptor,loader);

这行代码是调用函数dvmDefineClass从指定的Dex文件里,使用指定的类加载器加载指定类名称的类。


Thread* self = dvmThreadSelf();

if (dvmCheckException(self)) {

/*

* If we threw a "class not found"exception, stifle it, since the

* contract in the higher method says wesimply return null if

* the class is not found.

*/

Object* excep = dvmGetException(self);

if (strcmp(excep->clazz->descriptor,

"Ljava/lang/ClassNotFoundException;")== 0 ||

strcmp(excep->clazz->descriptor,

"Ljava/lang/NoClassDefFoundError;")== 0)

{

dvmClearException(self);

}

clazz = NULL;

}

这段代码是检查是否加载类的过程有异常发生。


/*

* Set the ProtectionDomain -- do we needthis to happen before we

* link the class and make it available? Ifso, we need to pass it

* through dvmDefineClass (and figure outsome other

* stuff, like where it comes from forbootstrap classes).

*/

if (clazz != NULL) {

//LOGI("SETTING pd '%s' to %p\n",clazz->descriptor, pd);

dvmSetFieldObject((Object*) clazz,gDvm.offJavaLangClass_pd, pd);

}

这段代码是设置类的保护域。



free(descriptor);

RETURN_PTR(clazz);

这行代码返回已经加载的类代码。


}

接着来分析函数dvmDefineClass,它的代码如下:

ClassObject*dvmDefineClass(DvmDex*pDvmDex, constchar*descriptor,

Object*classLoader)

{

这个函数输入的参数有三个,第一个pDvmDex参数是表示Dex文件对象,第二个descriptor参数是需要加载类名称,第三个classLoader参数是类的加载对象。


assert(pDvmDex !=NULL);


returnfindClassNoInit(descriptor, classLoader, pDvmDex);

这行代码调用函数findClassNoInit继续初始化。

}



接着下来分析函数findClassNoInit,它的代码如下:

staticClassObject*findClassNoInit(constchar*descriptor, Object*loader,

DvmDex*pDvmDex)

{

这个函数输入的参数有三个,第一个pDvmDex参数是表示Dex文件对象,第二个descriptor参数是需要加载类名称,第三个classLoader参数是类的加载对象。


Thread*self = dvmThreadSelf();

这行代码是获取当前加载类代码的线程。


ClassObject*clazz;

#ifdefWITH_PROFILER

bool profilerNotified = false;

#endif


if(loader != NULL) {

LOGVV("####findClassNoInit(%s,%p,%p)\n",descriptor, loader,

pDvmDex->pDexFile);

}

这段代码输出,当加载类对象不为空时,就调试输出加载类的名称、类的加载器和Dex文件对象。



/*

* We don't expect anexception to be raised at this point. The

* exception handling code isgood about managing this. This *can*

* happen if a JNI lookupfails and the JNI code doesn't do any

* error checking before doinganother class lookup, so we may just

* want to clear this andrestore it on exit. If we don't, some kinds

* of failures can't bedetected without rearranging other stuff.

*

* Most often when we hit thissituation it means that something is

* broken in the VM or in JNIcode, so I'm keeping it in place (and

* making it an informativeabort rather than an assert).

*/

if(dvmCheckException(self)) {

LOGE("Classlookup %s attempedwhile exception %s pending\n",

descriptor,dvmGetException(self)->clazz->descriptor);

dvmDumpAllThreads(false);

dvmAbort();

}

这段代码处理有异常的情况出现,把异常先处理掉。

clazz= dvmLookupClass(descriptor, loader, true);

if(clazz == NULL) {

constDexClassDef*pClassDef;

这段代码是调用函数dvmLookupClass在已经加载的类里查找是否已经存在,如果已经存在,就直接使用就可以了。否则,就需要从Dex文件里读取出来。



#ifdefWITH_PROFILER

dvmMethodTraceClassPrepBegin();

profilerNotified = true;

#endif


#ifLOG_CLASS_LOADING

u8 startTime =dvmGetThreadCpuTimeNsec();

#endif


if(pDvmDex == NULL) {

assert(loader ==NULL); /* shouldn't be hereotherwise */

pDvmDex =searchBootPathForClass(descriptor, &pClassDef);

这段代码是当判断输入的参数Dex文件为空时,说明需要从系统缺省的库目录里加载,就是调用searchBootPathForClass函数去查找相应的类,并返回这个类的Dex文件对象。


} else{

pClassDef =dexFindClass(pDvmDex->pDexFile,descriptor);

这行代码是当指定从Dex文件加载时,就调用函数dexFindClass从文件查找相应的类。


}



if(pDvmDex == NULL || pClassDef == NULL) {

if(gDvm.noClassDefFoundErrorObj!= NULL) {

/*usual case -- use prefabricatedobject */

dvmSetException(self,gDvm.noClassDefFoundErrorObj);

这段代码是判断是否加载出错,如果出错就设置异常返回。

} else{

/*dexoptcase -- can't guarantee prefab(core.jar) */

dvmThrowExceptionWithClassMessage(

"Ljava/lang/NoClassDefFoundError;",descriptor);

}


这里都没有办法加载到指定类出错返回。

gotobail;

}

/*found a match, try to load it */

clazz =loadClassFromDex(pDvmDex, pClassDef, loader);

这行代码是已经发现合适的类,并且找到相应的Dex文件时,就可以调用函数loadClassFromDex来加载类到内存,并准备好可以运行的状态。


if(dvmCheckException(self)) {

/*class was found but had issues */

dvmReleaseTrackedAlloc((Object*)clazz, NULL);

gotobail;

}

这段代码是当从Dex文件时加载类出错时返回。



/*

* Lock the class while welink it so other threads must wait for us

* to finish. Set the"initThreadId" so we can identify recursive

* invocation.

*/

dvmLockObject(self,(Object*)clazz);

clazz->initThreadId= self->threadId;

这段代码是锁住别的线程访问,然后设置这个类的初始化线程的ID,以便防止递归调用时可以判断出来。



/*

* Add to hash table solookups succeed.

*

* [Are circularreferences possible when linking a class?]

*/

下面这段代码是添加新加载的类到hash表里,以便后面查找快速。


assert(clazz->classLoader== loader);

if(!dvmAddClassToHash(clazz)) {

这行代码是调用dvmAddClassToHash函数来添加新加载的类clazzhash也就是保存在gDvm.loadedClasses。但是添加到hash表里,也不是每次成功的,因为加载类是多线程进行的,可能同一个类在不同的线程里加载了,另一个线程加载就不成功,因此dvmAddClassToHash函数会返回插入不成功,这时就需要进行下面的处理了。


/*

* Another thread musthave loaded the class after we

* started but beforewe finished. Discard what we've

* done and leave somehints for the GC.

*

* (Yes, thishappens.)

*/

//LOGW("WOW:somebody loaded %s simultaneously\n", descriptor);

clazz->initThreadId= 0;

dvmUnlockObject(self,(Object*)clazz);

这段代码是发现已经有别的线程加载同一个类,就设置这个类初始化的线程为空,然后释放这个线程锁。


/*Let the GC free the class.

*/

assert(clazz->obj.clazz== gDvm.unlinkedJavaLangClass);

dvmReleaseTrackedAlloc((Object*)clazz, NULL);

这段代码释放分配类占用的内存。


/*Grab the winning class.

*/

clazz =dvmLookupClass(descriptor, loader, true);

assert(clazz != NULL);

gotogot_class;

这段代码是从已经加载的类引用一份就可以返回给调用函数了。

接着下来,来分析怎么样加载方法的代码,loadMethodFromDex函数的代码如下:

staticvoidloadMethodFromDex(ClassObject*clazz, const DexMethod*pDexMethod,

Method*meth)

{

这个函数传入三个参数,第一个参数clazz是类相关信息;第二个参数pDexMethod是要加载的方法相关信息;第三个参数meth是指向加载的方法,也就是可以运行的代码。


DexFile*pDexFile = clazz->pDvmDex->pDexFile;

这行代码是获取Dex文件。


constDexMethodId*pMethodId;

constDexCode*pDexCode;


pMethodId =dexGetMethodId(pDexFile, pDexMethod->methodIdx);

这行代码是从Dex文件里通过索引获取到方法的ID。


meth->name= dexStringById(pDexFile, pMethodId->nameIdx);

这行代码是获取方法的名称。


dexProtoSetFromMethodId(&meth->prototype,pDexFile, pMethodId);

meth->shorty= dexProtoGetShorty(&meth->prototype);

meth->accessFlags= pDexMethod->accessFlags;

meth->clazz= clazz;

meth->jniArgInfo= 0;

这段代码是设置方法的属性。



if(dvmCompareNameDescriptorAndMethod("finalize","()V",meth) == 0) {

SET_CLASS_FLAG(clazz,CLASS_ISFINALIZABLE);

}

这段代码是判断是否最后退出函数,如果是退出函数就设置标志。



pDexCode =dexGetCode(pDexFile, pDexMethod);

这行代码是获取方法执行代码,如果是本地方法或者抽像函数返回空。


if(pDexCode != NULL) {

/*integer constants, copy over for faster access */

meth->registersSize= pDexCode->registersSize;

meth->insSize= pDexCode->insSize;

meth->outsSize= pDexCode->outsSize;


/*pointer to code area */

meth->insns= pDexCode->insns;

这段代码是从Dex文件里获取到方法的代码,保存相应的方法结构里,以便后面使用。


} else{

/*

* We don't have a DexCodeblock, but we still want to know how

* much space is neededfor the arguments (so we don't have to

* compute it later). Wealso take this opportunity to compute

* JNI argument info.

*

* We do this for abstractmethods as well, because we want to

* be able to substituteour exception-throwing "stub" in.

*/

intargsSize = dvmComputeMethodArgsSize(meth);

这行代码是计算方法参数大小。


if(!dvmIsStaticMethod(meth))

argsSize++;

这段代码是静态方法需要增加参数。


meth->registersSize= meth->insSize= argsSize;

assert(meth->outsSize== 0);

assert(meth->insns== NULL);


if(dvmIsNativeMethod(meth)) {

meth->nativeFunc= dvmResolveNativeMethod;

meth->jniArgInfo= computeJniArgInfo(&meth->prototype);

}

这段代码是处理本地方法的调用。


}

}


到这里,就可以把每个方法的代码解释出来,获取到方法所有的相关信息,这样就可以备运行条件了。

 

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐