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方法的第二步重要工作。