<!-- @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");
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 != ; cp++)
if (*cp == .)
*cp = /;
這一段代碼是把類名稱 com.android.internal.os.ZygoteInit轉換為 com/android/internal/os/ZygoteInit,為什麼要轉換這樣的方式呢?其實仔細思考一下,發現這不正是linux下的目錄表示方式嗎?是的,就是把類的點串連符變換為目錄方式,這要就可以到相應的目錄裡找到執行的代碼檔案。
LOGD("caijs add: JavaVM load class %s", slashClassName);
startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
LOGE("JavaVM unable to locate class %s", 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", 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", 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");
if (mJavaVM->DetachCurrentThread() != JNI_OK)
LOGW("Warning: unable to detach main thread");
if (mJavaVM->DestroyJavaVM() != 0)
LOGW("Warning: VM did not shut down cleanly");
這一段代碼是關閉所有虛擬機器時調用,基本上不會調用這段代碼的。
bail:
free(slashClassName);
這一行代碼是初始化出錯時調用。
}
在一節裡,學習了Davlik虛擬機器三大介面函數:FindClass、GetStaticMethodID和 CallStaticVoidMethod。其實理解起來很簡單,就是通過FindClass介面尋找到相應的Java類代碼,然後在這個類代碼用GetStaticMethodID介面尋找到相應的方法ID,最後通過 CallStaticVoidMethod介面運行相應的方法代碼,就完成Java代碼進入虛擬機器運行了。