【Android JNI】JNIEnv和JavaVM的區別


 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,否則會出錯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM