<!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } -->
从上一节可以知道Dalvik虚拟机入口点和创建虚拟机的函数,这一节继续分析运行时类调用虚拟机的代码片段,需要搞清楚怎么样运行JAVA的ZygoteInit类,Dalvik虚拟机又提供什么样的接口调用。运行时类代码如下:
/* start the virtual machine */
if (startVm(&mJavaVM, &env) != 0)
goto bail;
这一段是创建虚拟机,并准备好所有运行dex代码的环境。
/*
* Register android functions.
*/
if (startReg(env) < 0) {
LOGE("Unable to register all android natives/n");
goto bail;
}
这一段是注册所有android提供的本地方法,所谓的本地方法,其实是相对于JAVA里定义的方法,比如像C或C++等提供二进制运行的方法。
/*
* We want to call main() with a String array with arguments in it.
* At present we only have one argument, the class name. Create an
* array to hold it.
*/
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
jstring startSystemServerStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(2, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
startSystemServerStr = env->NewStringUTF(startSystemServer ?
"true" : "false");
env->SetObjectArrayElement(strArray, 1, startSystemServerStr);
这一段代码主要作用是把类型参数className转换为虚拟机里调用方法的参数,就是转为 strArray数组表示,同时可以添加多个参数。
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
jclass startClass;
jmethodID startMeth;
slashClassName = strdup(className);
这一行代码是拷贝类名称。
for (cp = slashClassName; *cp != '/0'; cp++)
if (*cp == '.')
*cp = '/';
这一段代码是把类名称 com.android.internal.os.ZygoteInit转换为 com/android/internal/os/ZygoteInit,为什么要转换这样的方式呢?其实仔细思考一下,发现这不正是linux下的目录表示方式吗?是的,就是把类的点连接符变换为目录方式,这要就可以到相应的目录里找到执行的代码文件。
LOGD("caijs add: JavaVM load class '%s'/n", slashClassName);
startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
LOGE("JavaVM unable to locate class '%s'/n", slashClassName);
/* keep going */
} else {
这一段代码主要通过类目录结构com/android/internal/os/ZygoteInit,查找到类的代码,查找到了就保存在startClass变量里。到这里,已经接触到Dalvik虚拟机提供了最重要的一个方法,它就是 FindClass方法接口,这个接口比较强大,只要提供类的目录结构,就可以找到相应的执行代码,这样就可以找类相关的方法入口,才可以给虚拟机解释器执行。因此,后面要好好了解和分析这个接口的实现。
startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
LOGE("JavaVM unable to find main() in '%s'/n", className);
/* keep going */
} else {
这一段代码是查找方法入口点的ID。主要就是在前面找到的类代码基础之上,然后通过方法名称“main”调用GetStaticMethodID接口,查找到方法的ID。这个方法ID是给后面虚拟机运行这个方法使用的,因为一个类里有很多方法,每个方法都有一个ID,只有通过这个ID才可以找到相应的方法来运行。在这段代码里,有一个特别的地方,就是 GetStaticMethodID方法最后一个参数“([Ljava/lang/String;)V”。这个参数是一个字符串,但内容排列比较奇怪,其实它是一种对函数返回值和参数的编码。这种编码叫做JNI字段描述符(Java Native Interface Field Descriptors)。
LOGD("caijs add: JavaVM find main() in '%s'/n", className);
env->CallStaticVoidMethod(startClass, startMeth, strArray);
这一行代码主要调用虚拟机的接口CallStaticVoidMethod 来运行 com.android.internal.os.ZygoteInit类里的main方法。在Android系统里,运行这个类代码之后,就不再返回来了,这个进程就变成虚拟机的主进程,这个虚拟机就变成主要运行ZygoteInit类的虚拟机了,其它应用程序的虚拟机都是从这个虚拟克隆出来,以达到快速地产生派生的虚拟机,每个应用程序一个虚拟机的健壮性、安全性。
#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
LOGD("Shutting down VM/n");
if (mJavaVM->DetachCurrentThread() != JNI_OK)
LOGW("Warning: unable to detach main thread/n");
if (mJavaVM->DestroyJavaVM() != 0)
LOGW("Warning: VM did not shut down cleanly/n");
这一段代码是关闭所有虚拟机时调用,基本上不会调用这段代码的。
bail:
free(slashClassName);
这一行代码是初始化出错时调用。
}
在一节里,学习了Davlik虚拟机三大接口函数:FindClass、GetStaticMethodID和 CallStaticVoidMethod。其实理解起来很简单,就是通过FindClass接口查找到相应的Java类代码,然后在这个类代码用GetStaticMethodID接口查找到相应的方法ID,最后通过 CallStaticVoidMethod接口运行相应的方法代码,就完成Java代码进入虚拟机运行了。