4.2 ZygoteInit的启动过程
AppRuntime的实现代码位于app_main.cpp中,它是AndroidRuntime类的派生类,其start方法便是继承自AndroidRuntime。
AndroidRuntime负责开启Android运行时环境,位于frameworks/base/core/jni/AndroidRuntime.cpp中。定位到start方法,其代码如下:
/*
*init.rc中指定了参数—zygote,那么传入到这个函数的参数分别是:
*className=com.android.internal.os.ZygoteInit
*options=start-system-server
*/
void AndroidRuntime:start(const charclassName, const charoptions)
{
/阻塞SIGPIPE信号,防止程序终止/
blockSigpipe();
……//省略部分内容
const char*rootDir=getenv("ANDROID_ROOT");
if(rootDir==NULL){
rootDir="/system";
……
setenv("ANDROID_ROOT",rootDir,1);//设置环境变量
}
/开启虚拟机/
JNIEnv*env;
if(startVm(&mJavaVM,&env)!=0){
return;
}
/*开启虚拟机后执行的工作,这里是空函数体,什么都没有做。运行时,实
际是调用的AppRuntime的onVmCreated,保存了类的全局引用/
onVmCreated(env);
/注册Android JNI函数/
if(startReg(env)<0){
return;
}
/*调用指定类的main方法,并传入参数className和options。在这之前需要
将参数className和参数options通过JNI函数转化为Java可识别的类型/
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;//用于转换className为Java可识别的字符串
jstring optionsStr;//用于转换options为Java可识别的字符串
stringClass=env->FindClass("java/lang/String");
/调用JNI函数生成一个对象数组,数组大小为2,存放字符串类型的元素/
strArray=env->NewObjectArray(2,stringClass, NULL);
classNameStr=env->NewStringUTF(className);
/将转化后的className存入数组/
env->SetObjectArrayElement(strArray,0,classNameStr);
optionsStr=env->NewStringUTF(options);
/将转化后的options存入数组/
env->SetObjectArrayElement(strArray,1,optionsStr);
/*将className转化为slash格式的字符串,以符合JNI命名规则。
*这里会将com.android.internal.os.ZygoteInit转化为
*com/android/internal/os/ZygoteInit,用于通过JNI函数FindClass
找到这个Java类/
char*slashClassName=toSlashClassName(className);
jclass startClass=env->FindClass(slashClassName);
if(startClass==NULL){
}else{
/得到指定类ZygoteInit的main方法的方法ID,即jmethodID/
jmethodID startMeth=env->GetStaticMethodID(startClass,"main",
"([Ljava/lang/String;)V");
if(startMeth==NULL){
}else{
/*通过JNI函数CallStaticVoidMethod调用Java类ZygoteInit的main方
法,并传入参数com.android.internal.os.ZygoteInit和参数true/
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
}
free(slashClassName);//回收资源
/detach当前线程,释放JNI资源并关闭虚拟机/
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");
}
AndroidRuntime的start方法开启了Android运行时(Android Runtime)。可以将它所做的工作分为三部分:
1)创建Dalvik虚拟机;
2)注册JNI方法;
3)开启Java世界。
下面详细分析这三部分。
4.2.1 创建Dalvik虚拟机
先来分析AndroidRuntime的start方法中的第一步:创建Dalvik虚拟机。这部分工作是在startVM方法中完成的,该方法位于AndroidRuntime.cpp中,代码如下:
int AndroidRuntime:startVm(JavaVMpJavaVM, JNIEnvpEnv)
{
int result=-1;
JavaVMInitArgs initArgs;
JavaVMOption opt;//虚拟机配置参数
char propBuf[PROPERTY_VALUE_MAX];
……//省略部分内容
/*通过属性系统获得dalvik.vm.checkjni的配置参数。checkjni是开发
*阶段一个非常重要的属性,通过它可以对JNI调用中的参数等信息自动进行
*检查,还可以定位一些JNI产生的异常,比如JNI的内存泄露。如果checkjni
*被设置为true, JNI调用过程就可以由dalvikvm记录并显示在logcat
*中,JNI检查会大幅度降低性能,只能在eng或者userdebug版本中配置。
由于JNI检查可能过于严格,导致检查不通过,这时调用进程就会异常退出/
property_get("dalvik.vm.checkjni",propBuf,"");
……//省略部分内容
/*当虚拟机收到SIGQUIT信号时,会将线程堆栈信息写入指定文件。这样可以保留异常
发生时的现场,方便调试和定位出错原因/
property_get("dalvik.vm.stack-trace-file",stackTraceFileBuf,"");
/对优化过的DEX文件进行检验的配置参数。关闭后会提升性能,但如果文件损坏会令虚拟机崩溃/
property_get("dalvik.vm.check-dex-sum",propBuf,"");
……//省略部分配置参数
strcpy(jniOptsBuf,"-Xjniopts:");//check jni相关
property_get("dalvik.vm.jniopts",jniOptsBuf+10,"");
……//省略部分内容
/enable verbose;standard options are{jni, gc, class}/
opt.optionString="-verbose:gc";
mOptions.add(opt);
/*设置虚拟机heap的大小,包括启动值(heapstartsize)和最大值(heapsize)。
*虚拟机分配heap内存,并不是一次将默认的16MB全部分配,而是首先只分配4MB,以后
*根据需要增加,当超过默认的16MB时,便会出现内存不足的现象。默认值偏小,实际
产品中可以自行调大,一般厂商会把最大值调整为32MB或者64MB/
strcpy(heapstartsizeOptsBuf,"-Xms");
property_get("dalvik.vm.heapstartsize",heapstartsizeOptsBuf+4,"4m");
opt.optionString=heapstartsizeOptsBuf;
mOptions.add(opt);
strcpy(heapsizeOptsBuf,"-Xmx");
property_get("dalvik.vm.heapsize",heapsizeOptsBuf+4,"16m");
opt.optionString=heapsizeOptsBuf;
mOptions.add(opt);
……//省略部分内容
if(checkJni){
opt.optionString="-Xcheck:jni";
mOptions.add(opt);
/check jni的检查项,限定JNI全局引用的个数不能超过2000/
opt.optionString="-Xjnigreflimit:2000";
mOptions.add(opt);
}
……//省略部分内容
/Set the properties for locale/
{
char langOption[sizeof("-Duser.language=")+3];
char regionOption[sizeof("-Duser.region=")+3];
strcpy(langOption,"-Duser.language=");
strcpy(regionOption,"-Duser.region=");
readLocale(langOption, regionOption);
opt.extraInfo=NULL;
opt.optionString=langOption;
mOptions.add(opt);
opt.optionString=regionOption;
mOptions.add(opt);
}
……//省略部分内容
/*创建虚拟机对象及JNIEnv对象,pJavaVm即传入的JavaVM指针,pEnv即传入
的JNIEnv指针,JavaVM是进程相关变量,JNIEnv*是线程相关变量。执行
成功后,虚拟机便准备就绪,这时候Java世界就可以分发JNI调用了/
if(JNI_CreateJavaVM(pJavaVM, pEnv,&initArgs)<0){
goto bail;
}
result=0;
bail:
free(stackTraceFile);
return result;
}
startVm主要做了两部分工作:
1)通过属性系统获取大量虚拟机配置信息,以此设置虚拟机参数。
2)调用JNI_CreateJavaVM方法创建虚拟机。
虚拟机参数很多,读者可以通过dalvik/docs/dexopt.html文件查看虚拟机的详细参数,也可以通过adb命令在手机上查看。在终端中执行以下命令:
adb shell dalvikvm
创建虚拟机的过程不再详解,有兴趣的读者可以自行研究。下面继续分析start方法的第二步重要工作。