<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                [原文出處-----------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所示: ![](https://box.kancloud.cn/18b1730a3379cb6388a5219784f7bc8c_764x971.jpeg) :-: 圖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虛擬機進程和線程與本地操作系統進程的線程的關系,敬請關注!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看