[原文出處-----------Dalvik虛擬機JNI方法的注冊過程分析](http://blog.csdn.net/luoshengyang/article/details/8923483)
在前面一文中,我們分析了Dalvik虛擬機的運行過程。從中可以知道,Dalvik虛擬機在調用一個成員函數的時候,如果發現該成員函數是一個JNI方法,那么就會直接跳到它的地址去執行。也就是說,JNI方法是直接在本地操作系統上執行的,而不是由Dalvik虛擬機解釋器執行。由此也可看出,JNI方法是Android應用程序與本地操作系統直接進行通信的一個手段。在本文中,我們就詳細分析JNI方法的注冊過程。
在Android系統中,JNI方法是以C/C++語言來實現的,然后編譯在一個so文件里面。這個JNI方法在能夠被調用之前,首先要加載到當前應用程序進程的地址空間來,如下所示:
~~~
package shy.luo.jni;
public class ClassWithJni {
......
static {
System.loadLibrary("nanosleep");
}
......
private native int nanosleep(long seconds, long nanoseconds);
......
}
~~~
上述代碼假設ClassWithJni類有一個JNI方法nanosleep,它實現在一個名稱為libnanosleep.so的文件中,因此,在該JNI方法能夠被調用之前,我們首先要將它加載到當前應用程序進程來,這是通過調用System類的靜態成員函數loadLibrary來實現的。
JNI方法nanosleep的實現如下所示:
~~~
#include "jni.h"
#include "JNIHelp.h"
#include <time.h>
static jint shy_luo_jni_ClassWithJni_nanosleep(JNIEnv* env, jobject clazz, jlong seconds, jlong nanoseconds)
{
struct timespec req;
req.tv_sec = seconds;
req.tv_nsec = nanoseconds;
return nanosleep(&req, NULL);
}
static const JNINativeMethod method_table[] = {
{"nanosleep", "(JJ)I", (void*)shy_luo_jni_ClassWithJni_nanosleep},
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
jniRegisterNativeMethods(env, "shy/luo/jni/ClassWithJni", method_table, NELEM(method_table));
return JNI_VERSION_1_4;
}
~~~
假設上述函數經過編譯之后,就位于一個名稱為libnanosleep.so的文件。
當libnanosleep.so文件被加載的時候,函數JNI_OnLoad就會被調用。在函數JNI_OnLoad中,參數vm描述的是當前進程中的Dalvik虛擬機,通過調用它的成員函數GetEnv就可以獲得一個JNIEnv對象。有了這個JNIEnv對象之后,我們就可以調用另外一個函數jniRegisterNativeMethods來向當前進程中的Dalvik虛擬機注冊一個JNI方法shy_luo_jni_ClassWithJni_nanosleep。這個JNI方法即為shy.luo.jni.ClassWithJni類的成員函數nanasleep的實現。
JNI方法shy_luo_jni_ClassWithJni_nanosleep要做的事情實際上就是通過系統調用nanosleep來使得當前進程進入睡眠狀態,直至seconds秒nanoseconds納秒之后再喚醒。使用系統調用nanosleep來使得當前進程進入睡眠狀態的好處它的時間精度可以達到納秒級,但是這個系統調用有兩個地方是需要注意的:
1. 如果進程在睡眠的過程中接收到信號,那么它就會提前被喚醒,這時候系統調用nanosleep的返回值為-1,并且錯誤代碼errno被設置為EINTR。
2. 如果CPU的時鐘中斷精度達不到納秒級別,那么nanosleep的睡眠精度也達不到納秒級,也就是說,當前進程不一定能在指定的納秒之后被喚醒,會有一定的延時。
不過,JNI方法shy_luo_jni_ClassWithJni_nanosleep的實現不是我們的重點,我們的重點是分析它注冊到Dalvik虛擬機的過程。
前面提到,JNI方法shy_luo_jni_ClassWithJni_nanosleep是libnanosleep.so文件加載的時候被注冊到Dalvik虛擬機的,因此,我們就從libnanosleep.so文件的加載開始,分析JNI方法shy_luo_jni_ClassWithJni_nanosleep注冊到Dalvik虛擬機的過程,也就是從System類的靜態成員函數loadLibrary開始分析一個JNI方法注冊到Dalvik虛擬機的過程,如圖1所示:

:-: 圖1 JNI方法注冊到Dalvik虛擬機的過程
這個過程可以分為12個步驟,接下來我們就詳細分析每一個步驟。
**Step 1. System.loadLibrary**
~~~
public final class System {
......
public static void loadLibrary(String libName) {
SecurityManager smngr = System.getSecurityManager();
if (smngr != null) {
smngr.checkLink(libName);
}
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
......
}
~~~
這個函數定義在文件libcore/luni/src/main/java/java/lang/System.java中。
System類的成員函數loadLibrary首先調用SecurityManager類的成員函數checkLink來進行安全檢查,即檢查名稱為libName的so文件是否允許加載。注意,這是Java的安全代碼檢查機制,而不是Android系統的安全檢查機制,而且Android系統沒有使用它來進行安全檢查。因此,這個檢查總是能通過的。
System類的成員函數loadLibrary接下來就再通過運行時類Runtime的成員函數loadLibrary來加載名稱為libName的so文件,接下來我們就繼續分析它的實現。
**Step 2. Runtime.loadLibrary**
~~~
public class Runtime {
......
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError("Couldn't load " + libraryName + ": " +
"findLibrary returned null");
}
String error = nativeLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : mLibPaths) {
String candidate = directory + filename;
candidates.add(candidate);
if (new File(candidate).exists()) {
String error = nativeLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
......
}
~~~
這個函數定義在文件libcore/luni/src/main/java/java/lang/Runtime.java中。
在Runtime類的成員函數loadLibrary中,參數libraryName表示要加載的so文件,而參數loader表示與要加載的so文件所關聯的類的一個類加載器。例如,在我們這個情景中,libraryName等于“nanosleep”,與它所關聯的類為shy.luo.jni.ClassWithJni。每一類有一個關聯的類加載器,用來負責加載該類。在Dalvik虛擬機中,類加載器除了知道它要加載的類所在的文件路徑之外,還知道該類所屬的APK用來保存so文件的路徑。因此,給定一個so文件名稱,一個類加載器可以判斷它是否存在自己的so文件目錄中。
參數libraryName只是描述要加載的so文件的部分名稱,它的完整名稱需要根據本地操作系統的特證來確定。由于目前Android系統都是屬于Linux系統,而在Linux系統中,so文件的命名規范通常就是lib<name>.so的形式,因此,在我們這個情景中,名稱為“nanosleep”的so文件的完整名稱就為“libnanosleep.so”,這是通過調用System類的靜態成員函數mapLibraryName來獲得的。
上面所獲得的libnanosleep.so文件的名稱仍然還不夠完整,因為它沒有包含絕對路徑。在這種情況下,我們是無法將它加載到Dalvik虛擬機中去的。當參數loader的值不等于null的時候,Runtime類的成員函數loadLibrary就會調用它的成員函數findLibrary來它的so文件目錄中尋找是否有一外名稱為“libnanosleep.so”。如果存在的話,那么就會返回該libnanosleep.so文件的絕對路徑。有了libnanosleep.so文件的絕對路徑之后,就可以調用Runtime類的另外一個成員函數nativeLoad來將它加載到當前進程的Dalvik虛擬機中。注意,將參數libraryName轉換為lib<name>.so的完整形式,以及獲得該so文件的絕對路徑,都是由參數loader所描述的一個類加載器的成員函數findLibrary來完成的。
另一方面,如果參數loader的值等于null,那么就表示當前要加載的so文件要在系統范圍的so文件目錄查找。這些系統范圍的so文件目錄保存在Runtime類的成員變量mLibPaths所描述的一個String數組中。通過依次檢查這些目錄是否存在與參數libraryName對應的so文件,就可以確定參數libraryName所指定加載的so文件是否是一個合法的so文件。如果合法的話,那么同樣會調用Runtime類的另外一個成員函數nativeLoad來將它加載到當前進程的Dalvik虛擬機中。注意,這里在檢查參數libraryName所表示的so文件是否存在于系統范圍的so文件目錄之前,同樣要將它轉換為lib<name>.so的形式,這同樣也是通過調用System類的靜態成員函數mapLibraryName來完成的。
如果最后無法在指定的APK或者系統范圍的so文件目錄中找到由參數libraryName所描述的so文件,或者找到了該so文件,但是在加載該so文件的過程中出現錯誤,那么Runtime類的成員函數loadLibrary都會拋出一個類型為UnsatisfiedLinkError的異常。
由于加載參數libraryName所描述的so文件是由Runtime類的成員函數nativeLoad來實現的,因此,接下來我們繼續分析它的實現。
**Step 3. Runtime.nativeLoad**
~~~
public class Runtime {
......
private static native String nativeLoad(String filename, ClassLoader loader);
......
}
~~~
這個函數定義在文件libcore/luni/src/main/java/java/lang/Runtime.java中。
Runtime類的成員函數nativeLoad是一個JNI方法。由于該JNI方法是屬于Java核心類Runtime的,也就是說,它在Dalvik虛擬機啟動的時候就已經在內部注冊過了,因此,這時候我們可以直接調用它注冊其它的JNI方法,也就是so文件filename里面所指定的JNI方法。Dalvik虛擬機在啟動過程中注冊Java核心類的操作,具體可以參考前面[Dalvik虛擬機的啟動過程分析](http://blog.csdn.net/luoshengyang/article/details/8885792)一文。
Runtime類的成員函數nativeLoad在C++層對應的函數為Dalvik_java_lang_Runtime_nativeLoad,如下所示:
~~~
static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
JValue* pResult)
{
StringObject* fileNameObj = (StringObject*) args[0];
Object* classLoader = (Object*) args[1];
char* fileName = NULL;
StringObject* result = NULL;
char* reason = NULL;
bool success;
assert(fileNameObj != NULL);
fileName = dvmCreateCstrFromString(fileNameObj);
success = dvmLoadNativeCode(fileName, classLoader, &reason);
if (!success) {
const char* msg = (reason != NULL) ? reason : "unknown failure";
result = dvmCreateStringFromCstr(msg);
dvmReleaseTrackedAlloc((Object*) result, NULL);
}
free(reason);
free(fileName);
RETURN_PTR(result);
}
~~~
這個函數定義在文件dalvik/vm/native/java_lang_Runtime.c中。
參數args[0]保存的是一個Java層的String對象,這個String對象描述的就是要加載的so文件,函數
Dalvik_java_lang_Runtime_nativeLoad首先是調用函數dvmCreateCstrFromString來將它轉換成一個C++層的字符串fileName,然后再調用函數dvmLoadNativeCode來執行加載so文件的操作。
接下來,我們就繼續分析函數dvmLoadNativeCode的實現,以便可以了解一個so文件的加載過程。
**Step 4. dvmLoadNativeCode**
~~~
bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
char** detail)
{
SharedLib* pEntry;
void* handle;
......
pEntry = findSharedLibEntry(pathName);
if (pEntry != NULL) {
if (pEntry->classLoader != classLoader) {
......
return false;
}
......
if (!checkOnLoadResult(pEntry))
return false;
return true;
}
......
handle = dlopen(pathName, RTLD_LAZY);
......
/* create a new entry */
SharedLib* pNewEntry;
pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
pNewEntry->pathName = strdup(pathName);
pNewEntry->handle = handle;
pNewEntry->classLoader = classLoader;
......
/* try to add it to the list */
SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
if (pNewEntry != pActualEntry) {
......
freeSharedLibEntry(pNewEntry);
return checkOnLoadResult(pActualEntry);
} else {
......
bool result = true;
void* vonLoad;
int version;
vonLoad = dlsym(handle, "JNI_OnLoad");
if (vonLoad == NULL) {
LOGD("No JNI_OnLoad found in %s %p, skipping init\n",
pathName, classLoader);
} else {
......
OnLoadFunc func = vonLoad;
......
version = (*func)(gDvm.vmList, NULL);
......
if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 &&
version != JNI_VERSION_1_6)
{
.......
result = false;
} else {
LOGV("+++ finished JNI_OnLoad %s\n", pathName);
}
}
......
if (result)
pNewEntry->onLoadResult = kOnLoadOkay;
else
pNewEntry->onLoadResult = kOnLoadFailed;
......
return result;
}
}
~~~
這個函數定義在文件dalvik/vm/Native.c中。
函數dvmLoadNativeCode首先是檢查參數pathName所指定的so文件是否已經加載過了,這是通過調用函數findSharedLibEntry來實現的。如果已經加載過,那么就可以獲得一個SharedLib對象pEntry。這個SharedLib對象pEntry描述了有關參數pathName所指定的so文件的加載信息,例如,上次用來加載它的類加載器和上次的加載結果。如果上次用來加載它的類加載器不等于當前所使用的類加載器,或者上次沒有加載成功,那么函數dvmLoadNativeCode就回直接返回false給調用者,表示不能在當前進程中加載參數pathName所描述的so文件。
我們假設參數pathName所指定的so文件還沒有被加載過,這時候函數dvmLoadNativeCode就會先調用dlopen來在當前進程中加載它,并且將獲得的句柄保存在變量handle中,接著再創建一個SharedLib對象pNewEntry來描述它的加載信息。這個SharedLib對象pNewEntry還會通過函數addSharedLibEntry被緩存起來,以便可以知道當前進程都加載了哪些so文件。
注意,在調用函數addSharedLibEntry來緩存新創建的SharedLib對象pNewEntry的時候,如果得到的返回值pActualEntry指向的不是SharedLib對象pNewEntry,那么就表示另外一個線程也正在加載參數pathName所指定的so文件,并且比當前線程提前加載完成。在這種情況下,函數addSharedLibEntry就什么也不用做而直接返回了。否則的話,函數addSharedLibEntry就要繼續負責調用前面所加載的so文件中的一個指定的函數來注冊它里面的JNI方法。
這個指定的函數的名稱為“JNI_OnLoad”,也就是說,每一個用來實現JNI方法的so文件都應該定義有一個名稱為“JNI_OnLoad”的函數,并且這個函數的原型為:
~~~
jint JNI_OnLoad(JavaVM* vm, void* reserved);
~~~
函數dvmLoadNativeCode通過調用函數dlsym就可以獲得在前面加載的so中名稱為“JNI_OnLoad”的函數的地址,最終保存在函數指針func中。有了這個函數指針之后,我們就可以直接調用它來執行注冊JNI方法的操作了。注意,在調用該JNI_OnLoad函數時,第一個要傳遞進行的參數是一個JavaVM對象,這個JavaVM對象描述的是在當前進程中運行的Dalvik虛擬機,第二個要傳遞的參數可以設置為NULL,這是保留給以后使用的。
從前面[Dalvik虛擬機的啟動過程分析](http://blog.csdn.net/luoshengyang/article/details/8885792)一文可以知道,在當前進程所運行的Dalvik虛擬機實例是通過全局變量gDvm所描述的一個DvmGlobals結構體的成員變量vmList來描述的,因此,我們就可以將它傳遞在前面加載的so中名稱中定義的JNI_OnLoad函數。注意,定義在該so文件中的JNI_OnLoad函數一旦執行成功,它的返回值就必須等于JNI_VERSION_1_2、JNI_VERSION_1_4或者JNI_VERSION_1_6,用來表示所注冊的JNI方法的版本。
最后, 函數dvmLoadNativeCode根據上述的JNI_OnLoad函數的執行成功與否,將前面所創建的一個SharedLib對象pNewEntry的成員變量onLoadResult設置為kOnLoadOkay或者kOnLoadFailed,這樣就可以記錄參數pathName所指定的so文件是否是加載成功的,也就是它是否成功地注冊了其內部的JNI方法。
在我們這個情景中,參數pathName所指定的so文件為libnanosleep.so,接下來我們就繼續分析它的函數JNI_OnLoad的實現,以便可以發解定義在它里面的JNI方法的注冊過程。
**Step 5. JNI_OnLoad**
定義在libnanosleep.so文件中的函數JNI_OnLoad的實現可以參考文章開始的部分。從它的實現可以知道,它所注冊的JNI方法shy_luo_jni_ClassWithJni_nanosleep是與shy.luo.jni.ClassWithJni類的成員函數nanosleep對應的,并且是通過調用函數jniRegisterNativeMethods來實現的。因此,接下來我們就繼續分析函數jniRegisterNativeMethods的實現。
**Step 6. jniRegisterNativeMethods**
~~~
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
LOGV("Registering %s natives\n", className);
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'\n", className);
return -1;
}
int result = 0;
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s'\n", className);
result = -1;
}
(*env)->DeleteLocalRef(env, clazz);
return result;
}
~~~
這個函數定義在文件dalvik/libnativehelper/JNIHelp.c中。
參數env所指向的一個JNIEnv結構體,通過調用這個JNIEnv結構體可以獲得參數className所描述的一個類。這個類就是要注冊JNI的類,而它所要注冊的JNI就是由參數gMethods來描述的。
注冊參數gMethods所描述的JNI方法是通過調用env所指向的一個JNIEnv結構體的成員函數RegisterNatives來實現的,因此,接下來我們就繼續分析它的實現。
**Step 7. JNIEnv.RegisterNatives**
~~~
typedef _JNIEnv JNIEnv;
......
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
......
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
jint nMethods)
{ return functions->RegisterNatives(this, clazz, methods, nMethods); }
......
}
~~~
這個函數定義在文件dalvik/libnativehelper/include/nativehelper/jni.h中。
從前面[Dalvik虛擬機的運行過程分析](http://blog.csdn.net/luoshengyang/article/details/8914953)一文可以知道,結構體JNIEnv的成員變量functions指向的是一個函數表,這個函數表又包含了一系列的函數指針,指向了在當前進程中運行的Dalvik虛擬機中定義的函數。對于結構體JNIEnv的成員函數RegisterNatives來說,它就是通過調用這個函數表中名稱為RegisterNatives的函數指針來注冊參數gMethods所描述的JNI方法的。
從前面[Dalvik虛擬機的啟動過程分析](http://blog.csdn.net/luoshengyang/article/details/8914953)一文可以知道,上述函數表中名稱為RegisterNatives的函數指針指向的是在Dalvik虛擬機內部定義的函數RegisterNatives,因此,接下來我們就繼續分析它的實現。
**Step 8. RegisterNatives**
~~~
static jint RegisterNatives(JNIEnv* env, jclass jclazz,
const JNINativeMethod* methods, jint nMethods)
{
JNI_ENTER();
ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);
jint retval = JNI_OK;
int i;
......
for (i = 0; i < nMethods; i++) {
if (!dvmRegisterJNIMethod(clazz, methods[i].name,
methods[i].signature, methods[i].fnPtr))
{
retval = JNI_ERR;
}
}
JNI_EXIT();
return retval;
}
~~~
這個函數定義在文件dalvik/vm/Jni.c中。
參數jclazz描述的是要注冊JNI方法的類,而參數methods描述的是要注冊的一組JNI方法,這個組JNI方法的個數由參數nMethods來描述。
函數RegisterNatives首先是調用函數dvmDecodeIndirectRef來獲得要注冊JNI方法的類對象,接著再通過一個for循環來依次調用函數dvmRegisterJNIMethod注冊參數methods描述所描述的每一個JNI方法。注意,每一個JNI方法都由名稱、簽名和地址來描述。
接下來,我們就繼續分析函數dvmRegisterJNIMethod的實現。
**Step 9. dvmRegisterJNIMethod**
~~~
static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
const char* signature, void* fnPtr)
{
Method* method;
bool result = false;
if (fnPtr == NULL)
goto bail;
method = dvmFindDirectMethodByDescriptor(clazz, methodName, signature);
if (method == NULL)
method = dvmFindVirtualMethodByDescriptor(clazz, methodName, signature);
if (method == NULL) {
LOGW("ERROR: Unable to find decl for native %s.%s:%s\n",
clazz->descriptor, methodName, signature);
goto bail;
}
if (!dvmIsNativeMethod(method)) {
LOGW("Unable to register: not native: %s.%s:%s\n",
clazz->descriptor, methodName, signature);
goto bail;
}
if (method->nativeFunc != dvmResolveNativeMethod) {
/* this is allowed, but unusual */
LOGV("Note: %s.%s:%s was already registered\n",
clazz->descriptor, methodName, signature);
}
dvmUseJNIBridge(method, fnPtr);
LOGV("JNI-registered %s.%s:%s\n", clazz->descriptor, methodName,
signature);
result = true;
bail:
return result;
}
~~~
這個函數定義在文件dalvik/vm/Jni.c中。
函數dvmRegisterJNIMethod在注冊參數methodName所描述的JNI方法之前,首先會進行一系列的檢查,包括:
1. 確保參數clazz所描述的類有一個名稱為methodName的成員函數。首先是調用函數dvmFindDirectMethodByDescriptor來檢查methodName是否是clazz的一個非虛成員函數,然后再調用函數dvmFindVirtualMethodByDescriptor來檢查methodName是否是clazz的一個虛成員函數。
2. 確保類clazz的成員函數methodName確實是聲明為JNI方法,即帶有native修飾符,這是通過調用函數dvmIsNativeMethod來實現的。
通過了前面的第1個檢查之后,就可以獲得一個Method對象method,用來描述要注冊的JNI方法所對應的Java類成員函數。當一個Method對象method描述的是一個JNI方法的時候,它的成員變量nativeFunc保存的就是該JNI方法的地址,但是在對應的JNI方法注冊進來之前,該成員變量的值被統一設置為dvmResolveNativeMethod。因此,當我們調用了一個未注冊的JNI方法時,實際上執行的是函數dvmResolveNativeMethod。函數dvmResolveNativeMethod此時會在Dalvik虛擬內部以及當前所有已經加載的共享庫中檢查是否存在對應的JNI方法。如果不存在,那么它就會拋出一個類型為java.lang.UnsatisfiedLinkError的異常。
注意,一個JNI方法是可以重復注冊的,無論如何,函數dvmRegisterJNIMethod都是調用另外一個函數dvmUseJNIBridge來繼續執行注冊JNI的操作。
**Step 10. dvmUseJNIBridge**
~~~
/**
* Returns true if the -Xjnitrace setting implies we should trace 'method'.
*/
static bool shouldTrace(Method* method)
{
return gDvm.jniTrace && strstr(method->clazz->descriptor, gDvm.jniTrace);
}
/*
* Point "method->nativeFunc" at the JNI bridge, and overload "method->insns"
* to point at the actual function.
*/
void dvmUseJNIBridge(Method* method, void* func)
{
DalvikBridgeFunc bridge = shouldTrace(method)
? dvmTraceCallJNIMethod
: dvmSelectJNIBridge(method);
dvmSetNativeFunc(method, bridge, func);
}
~~~
這個函數定義在文件dalvik/vm/Jni.c中。
一個JNI方法并不是直接被調用的,而是通過由Dalvik虛擬機間接地調用,這個用來間接調用JNI方法的函數就稱為一個Bridge。這些Bridage函數在真正調用JNI方法之前,會執行一些通用的初始化工作。例如,會將當前線程的狀態設置為NATIVE,因為它即將要執行一個Native函數。又如,會為即將要被調用的JNI方法準備好前面兩個參數,第一個參數是一個JNIEnv對象,用來描述當前線程的Java環境,通過它可以訪問反過來訪問Java代碼和Java對象,第二個參數是一個jobject對象,用來描述當前正在執行JNI方法的Java對象。
這些Bridage函數實際上仍然不是直接調用地調用JNI方法的,這是因為Dalvik虛擬機是可以運行在各種不同的平臺之上,而每一種平臺可能都定義有自己的一套函數調用規范,也就是所謂的ABI(Application Binary Interface),這是一個API(Application Programming Interface)不同的概念。ABI是在二進制級別上定義的一套函數調用規范,例如參數是通過寄存器來傳遞還是堆棧來傳遞,而API定義是一個應用程序編程接口規范。換句話說,API定義了源代碼和庫之間的接口,因此同樣的代碼可以在支持這個API的任何系統中編譯 ,而ABI允許編譯好的目標代碼在使用兼容ABI的系統中無需改動就能運行。
為了使得運行在不同平臺上的Dalvik虛擬機能夠以統一的方法來調用JNI方法,這些Bridage函數使用了一個libffi庫,它的源代碼位于external/libffi目錄中。Libffi是一個開源項目,用于高級語言之間的相互調用的處理,它的實現機制可以進一步參考[libffi](http://www.sourceware.org/libffi/)。
回到函數dvmUseJNIBridge中,它主要就是根據Dalvik虛擬機的啟動選項來為即將要注冊的JNI選擇一個合適的Bridge函數。如果我們在Dalvik虛擬機啟動的時候,通過-Xjnitrace選項來指定了要跟蹤參數method所描述的JNI方法,那么函數dvmUseJNIBridge為該JNI方法選擇的Bridge函數就為dvmTraceCallJNIMethod,否則的話,就再通過另外一個函數dvmSelectJNIBridge來進一步選擇一個合適的Bridge函數。選擇好Bridge函數之后,函數dvmUseJNIBridge最終就調用函數dvmSetNativeFunc來執行真正的JNI方法注冊操作。
我們假設參數method所描述的JNI方法沒有設置為跟蹤,因此,接下來,我們就首先分析函數dvmSelectJNIBridge的實現,接著再分析函數dvmSetNativeFunc的實現。
**Step 11. dvmSelectJNIBridge**
~~~
/*
* Returns the appropriate JNI bridge for 'method', also taking into account
* the -Xcheck:jni setting.
*/
static DalvikBridgeFunc dvmSelectJNIBridge(const Method* method)
{
enum {
kJNIGeneral = 0,
kJNISync = 1,
kJNIVirtualNoRef = 2,
kJNIStaticNoRef = 3,
} kind;
static const DalvikBridgeFunc stdFunc[] = {
dvmCallJNIMethod_general,
dvmCallJNIMethod_synchronized,
dvmCallJNIMethod_virtualNoRef,
dvmCallJNIMethod_staticNoRef
};
static const DalvikBridgeFunc checkFunc[] = {
dvmCheckCallJNIMethod_general,
dvmCheckCallJNIMethod_synchronized,
dvmCheckCallJNIMethod_virtualNoRef,
dvmCheckCallJNIMethod_staticNoRef
};
bool hasRefArg = false;
if (dvmIsSynchronizedMethod(method)) {
/* use version with synchronization; calls into general handler */
kind = kJNISync;
} else {
/*
* Do a quick scan through the "shorty" signature to see if the method
* takes any reference arguments.
*/
const char* cp = method->shorty;
while (*++cp != '\0') { /* pre-incr to skip return type */
if (*cp == 'L') {
/* 'L' used for both object and array references */
hasRefArg = true;
break;
}
}
if (hasRefArg) {
/* use general handler to slurp up reference args */
kind = kJNIGeneral;
} else {
/* virtual methods have a ref in args[0] (not in signature) */
if (dvmIsStaticMethod(method))
kind = kJNIStaticNoRef;
else
kind = kJNIVirtualNoRef;
}
}
return dvmIsCheckJNIEnabled() ? checkFunc[kind] : stdFunc[kind];
}
~~~
這個函數定義在文件dalvik/vm/Jni.c中。
Dalvik虛擬機提供的Bridge函數主要是分為兩類。第一類Bridge函數在調用完成JNI方法之后,會檢查該JNI方法的返回結果是否與聲明的一致,這是因為一個聲明返回String的JNI方法在執行時返回的可能會是一個Byte Array。如果不一致,取決于Dalvik虛擬機的啟動選項,它可能會停機。第二類Bridge函數不對JNI方法的返回結果進行上述檢查。選擇哪一類Bridge函數可以通過-Xcheck:jni選項來決定。不過由于檢查一個JNI方法的返回結果是否與聲明的一致是很耗時的,因此,我們一般都不會使用第一類Bridge函數。
此外,每一類Bridge函數又分為四個子類:Genernal、Sync、VirtualNoRef和StaticNoRef,它們的選擇規則為:
1. 一個JNI方法的參數列表中如果包含有引用類型的參數,那么對應的Bridge函數就是Genernal類型的,即為dvmCallJNIMethod_general或者dvmCheckCallJNIMethod_general。
2. 一個JNI方法如果聲明為同步方法,即帶有synchronized修飾符,那么對應的Bridge函數就是Sync類型的,即為dvmCallJNIMethod_synchronized或者dvmCheckCallJNIMethod_synchronized。
3. 一個JNI方法的參數列表中如果不包含有引用類型的參數,并且它是一個虛成員函數,那么對應的Bridge函數就是kJNIVirtualNoRef類型的,即為dvmCallJNIMethod_virtualNoRef或者dvmCheckCallJNIMethod_virtualNoRef。
4. 一個JNI方法的參數列表中如果不包含有引用類型的參數,并且它是一個靜態成員函數,那么對應的Bridge函數就是StaticNoRef類型的,即為dvmCallJNIMethod_staticNoRef或者dvmCheckCallJNIMethod_staticNoRef。
每一類Bridge函數之所以要劃分為上述四個子類,是因為每一個子類的Bridge函數在調用真正的JNI方法之前,所要進行的準備工作是不一樣的。例如,Genernal類型的Bridge函數需要為引用類型的參數增加一個本地引用,避免它在JNI方法執行的過程中被回收。又如,Sync類型的Bridge函數在調用JNI方法之前,需要執行同步原始,以避免多線程訪問的競爭問題。
這一步執行完成之后,返回到前面的Step 10中,即函數dvmUseJNIBridge中,這時候它就獲得了一個Bridge函數,因此,接下來它就可以調用函數dvmSetNativeFunc來執行真正的JNI方法注冊操作了。
**Step 12. dvmSetNativeFunc**
~~~
void dvmSetNativeFunc(Method* method, DalvikBridgeFunc func,
const u2* insns)
{
......
if (insns != NULL) {
/* update both, ensuring that "insns" is observed first */
method->insns = insns;
android_atomic_release_store((int32_t) func,
(void*) &method->nativeFunc);
} else {
/* only update nativeFunc */
method->nativeFunc = func;
}
......
}
~~~
這個函數定義在文件dalvik/vm/oo/Class.c中。
參數method表示要注冊JNI方法的Java類成員函數,參數func表示JNI方法的Bridge函數,參數insns表示要注冊的JNI方法的函數地址。
當參數insns的值不等于NULL的時候,函數dvmSetNativeFunc就分別將參數insns和func的值分別保存在參數method所指向的一個Method對象的成員變量insns和nativeFunc中,而當insns的值等于NULL的時候,函數dvmSetNativeFunc就只將參數func的值保存在參數method所指向的一個Method對象成員變量nativeFunc中。
假設在前面的Step 11中選擇的Bridge函數為dvmCallJNIMethod_general,并且結合前面[Dalvik虛擬機的運行過程分析](http://blog.csdn.net/luoshengyang/article/details/8914953)一文,我們就可以得到Dalvik虛擬機在運行過程中調用JNI方法的過程:
1. 調用函數dvmCallJNIMethod_general,執行一些必要的準備工作;
2. 函數dvmCallJNIMethod_general再調用函數dvmPlatformInvoke來以統一的方式來調用對應的JNI方法;
3. 函數dvmPlatformInvoke通過libffi庫來調用對應的JNI方法,以屏蔽Dalvik虛擬機運行在不同目標平臺的細節。
至此,我們就分析完成Dalvik虛擬機JNI方法的注冊過程了。這樣,我們就打通了Java代碼和Native代碼之間的道路。實際上,很多Java和Android核心類的功能都是通過本地操作系統提供的系統調用來完成的,例如,Zygote類的成員函數forkAndSpecialize最終是通過Linux系統調用fork來創建一個Android應用程序進程的,又如,Thread類的成員函數start最終是通過pthread線程庫函數pthread_create來創建一個Android應用程序線程的。
在接下來的一篇文章中,我們就在Java代碼和Native代碼打通了的基礎上,分析Android應用程序進程和線程與本地操作系統進程的線程的關系,也就是Dalvik虛擬機進程和線程與本地操作系統進程的線程的關系,敬請關注!
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析