JNI的實現可涉及兩個關鍵類:JNIEnv和JavaVM。
- JavaVM:這個代表java的虛擬機。所有的工作都是從獲取虛擬機的接口開始的。
第一種方式,在加載動態鏈接庫的時候,JVM會調用JNI_OnLoad(JavaVM* jvm, void* reserved)(如果定義了該函數)。第一個參數會傳入JavaVM指針。
第二種方式,在native code中調用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)可以得到JavaVM指針。
兩種情況下,都可以用全局變量,比如JavaVM* g_jvm來保存獲得的指針以便在任意上下文中使用。
Android系統是利用第二種方式Invocation interface來創建JVM的。
- JNIEnv:JNI Interface Pointer, 是提供JNI Native函數的基礎環境,線程相關,不同線程的JNIEnv相互獨立。
JNIEnv只在當前線程中有效。本地方法不 能將JNIEnv從一個線程傳遞到另一個線程中。相同的 Java 線程中對本地方法多次調用時,傳遞給該本地方法的JNIEnv是相同的。但是,一個本地方法可被不同的 Java 線程所調用,因此可以接受不同的 JNIEnv。
JavaVM則可以在進程中的各線程間共享。理論上一個進程可以有多個JavaVM,但Android只允許一個(JavaVm and JIEnv)。需要強調的是JNIEnv是跟線程相關的。sdk文檔中強調了do not cache JNIEnv*,要用的時候在不同線程中再通過JavaVM *jvm的方法來獲取與當前線程相關的JNIEnv*。兩者都可以理解為函數表(Function Pointer Table), 前者是使用Java程序創建的運行環境(從屬於一個JVM)提供JNI Native函數。
- Java和Android中JavaVM對象有區別
在java里,每一個process可以產生多個java vm對象,但是在android上,每一個process只有一個Dalvik虛擬機對象,也就是在android進程中是通過有且只有一個虛擬器對象來服務所有java和c/c++代碼。
Java 的dex字節碼和c/c++的*.so同時運行Dalvik虛擬機之內,共同使用一個進程空間。之所以可以相互調用,也是因為有Dalvik虛擬機。當java 代碼需要c/c++代碼時,在Dalvik虛擬機加載進*.so庫時,會先調用JNI_Onload(),此時就會把JAVA VM對象的指針存儲於c層jni組件的全局環境中,在Java層調用C層的本地函數時,調用c本地函數的線程必然通過Dalvik虛擬機來調用c層的本地函數,此時,Dalvik虛擬機會為本地的C組件實例化一個JNIEnv指針,該指針指向Dalvik虛擬機的具體的函數列表,當JNI的c組件調用Java層的方法或者屬性時,需要通過JNIEnv指針來進行調用。 當本地c/c++想獲得當前線程所要使用的JNIEnv時,可以使用Dalvik虛擬機對象的
JavaVM* jvm->GetEnv()返回當前線程所在的JNIEnv*。
建立全局的JavaVM的方法:
1 //由java調用來建立JNI環境 2 JNIEXPORT void Java_com_nan_thread_MyThreadActivity_setJNIEnv( JNIEnv* env, jobject obj) 3 { 4 //保存全局JVM以便在子線程中使用 5 (*env)->GetJavaVM(env,&g_jvm); 6 //不能直接賦值(g_obj = obj) 7 g_obj = (*env)->NewGlobalRef(env,obj); 8 }
獲得JNIEvn方法:
1 JNIEnv *JNI_GetEnv(int *attach) { 2 if (JNI_Jvm == NULL) 3 return NULL; 4 5 int status; 6 7 //判斷是否有AttachCurrentThread 8 *attach = 0; 9 10 JNIEnv *_jniEnv = NULL; 11 12 status = (*JNI_Jvm)->GetEnv(JNI_Jvm, (void **) &_jniEnv, JNI_VERSION_1_6); 13 14 if (status == JNI_EDETACHED || _jniEnv == NULL) { 15 status = (*JNI_Jvm)->AttachCurrentThread(JNI_Jvm, &_jniEnv, NULL); 16 if (status < 0) { 17 _jniEnv = NULL; 18 } else { 19 *attach = 1; 20 } 21 } 22 return _jniEnv; 23 }
回收env:
1 int JNI_DelEnv() { 2 if (JNI_Jvm == NULL) 3 return -1; 4 5 return (*JNI_Jvm)->DetachCurrentThread(JNI_Jvm); 6 }
在java線程中不能使用AttachCurrentThread、DetachCurrentThread方法來獲取JNIEnv,否則會出錯。