[轉]JNIEnv解析

來源:互聯網
上載者:User

標籤:數組   his   roc   job   找不到   lag   roi   env   ons   

1.關於JNIEnv和JavaVM

 JNIEnv是一個與線程相關的變數,不同線程的JNIEnv彼此獨立。JavaVM是虛擬機器在JNI層的代表,在一個虛擬機器進程中只有一個JavaVM,因此該進程的所有線程都可以使用這個JavaVM。當後台線程需要調用JNI native時,在native庫中使用全域變數儲存JavaVM尤為重要,這樣使得後台線程能通過JavaVM獲得JNIEnv。

native程式中頻繁使用JNIEnv*和JavaVM*。而C和C++代碼使用JNIEnv*和JavaVM*這兩個指標的做法是有區別的,網上大部分代碼都使用C++,基本上找不到關於C和C++在這個問題上的詳細敘述。

在C中:

使用JNIEnv* env要這樣      (*env)->方法名(env,參數列表)

使用JavaVM* vm要這樣       (*vm)->方法名(vm,參數列表)

在C++中:

使用JNIEnv* env要這樣      env->方法名(參數列表)

使用JavaVM* vm要這樣       vm->方法名(參數列表)

上面這二者的區別是,在C中必須先對env和vm間接定址(得到的內容仍然是一個指標),在調用方法時要將env或vm傳入作為第一個參數。C++則直接利用env和vm指標調用其成員。那到底C中的(*env)和C++中的env是否有相同的資料類型呢?C中的(*vm) 和C++中的vm是否有相同的資料類型呢?

為了驗證上面的猜測,我們可以查看JNIEnv和JavaVM的定義。他們位於標頭檔jni.h。我開發JNI用的是Android-5平台,下面是 $NDK\platforms\android-5\arch-arm\usr\include\jni.h的部分代碼

struct _JNIEnv;    struct _JavaVM;    #if defined(__cplusplus)    typedef _JNIEnv JNIEnv;                                 //C++使用這個類型    typedef _JavaVM JavaVM;                                 //C++使用這個類型    #else    typedef const struct JNINativeInterface* JNIEnv;        //C使用這個類型    typedef const struct JNIInvokeInterface* JavaVM;        //C使用這個類型    #endif    struct JNINativeInterface    {        /****省略了的代碼****/        jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);        /****省略了的代碼****/        jobject     (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);        /****省略了的代碼****/    };    struct _JNIEnv  {      const struct JNINativeInterface* functions;      #if defined(__cplusplus)      /****省略了的代碼****/      jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)      { return functions->GetMethodID(this, clazz, name, sig); }      /****省略了的代碼****/      jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)      { return functions->GetStaticObjectField(this, clazz, fieldID); }      /****省略了的代碼****/      #endif /*__cplusplus*/  };    struct JNIInvokeInterface  {       /****省略了的代碼****/      jint (*GetEnv)(JavaVM*, void**, jint);      jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);  };    struct _JavaVM  {      const struct JNIInvokeInterface* functions;      #if defined(__cplusplus)      /****省略了的代碼****/      jint GetEnv(void** env, jint version)      { return functions->GetEnv(this, env, version); }      jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)      { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }      #endif /*__cplusplus*/  };

  

  

假如我們用C編碼,宏__cplusplus沒有定義,那麼從最上面的宏#if defined(__cplusplus)可推斷

JNIEnv    代表類型 const struct JNINativeInterface*

JavaVM   代表類型 const struct JNIInvokeInterface*

那麼JNIEnv* env實際上等價於聲明 const struct JNINativeInterface**  env

JavaVM* vm實際上等價於聲明 const struct JNIInvokeInterface ** vm

因此要調用JNINativeInterface結構體內的函數指標就必須先對env間接定址。

(*env)的類型是const struct JNINativeInterface*(指向JNINativeInterface結構體的指標),這時候可以用這個指標調用結構體的成員函數指標,(*env)-> GetMethodID(env, jclass, const char*, const char*)。同理可分析JavaVM* vm。

----------------------------------------------------------------------------------------------------------------------------------------------

假如我們用C++編碼,宏__cplusplus有定義,那麼從最上面的宏#if defined(__cplusplus)可推斷

JNIEnv    代表類型 struct _JNIEnv

JavaVM   代表類型 struct _JavaVM

那麼JNIEnv* env實際上等價於聲明 struct _JNIEnv*  env

JavaVM* vm實際上等價於聲明 struct _JavaVM* vm

要調用_JNIEnv結構體內的函數指標這直接使用env而不需間接定址, env-> GetMethodID(jclass, const char*, const char*)。同理可分析JavaVM* vm。

現在可以回答剛才的猜測了,C中的(*env)類型是const struct JNINativeInterface*,C++中的env類型是struct _JNIEnv*,因此他們的資料類型不相同(雖然都是指標,但指向不同的結構體類型)。

我們再看結構體_JNIEnv(C++的JNIEnv所代表的類型),這個結構體內有一個成員const struct JNINativeInterface* functions,再仔細看_JNIEnv內定義的函數。當調用_JNIEnv內定義的函數時,其實就是通過functions這個指標調用JNINativeInterface內的函數指標,因此_JNIEnv的成員方法是JNINativeInterface的同名成員函數指標的封裝而已,歸根結底無論在C還是C++中其實都使用了JNINativeInterface結構體。這時調用JNINativeInterface的函數指標的第一參數是this,在C++中this代表指向當前內容物件的指標其類型是struct _JNIEnv*(即JNIEnv*)。同理可分析_JavaVM。

2.註冊和登出native函數

C和C++註冊native函數的方式大致上相同,下面給出具體的代碼。

[cpp] view plaincopy  
  1. /* JNINativeMethod數組的定義在C和C++中都一樣*/  
  2. static JNINativeMethod gMethods[] = {  
  3.     {  
  4.         "jobjectProcess",  
  5.         "(Lcom/example/hellojni/HelloJni$Student;Ljava/lang/Integer;)V",  
  6.         (void*)jobjectProcess  
  7.     }  
  8.     /*被省略掉的代碼*/  
  9. };  
  10.   
  11. jint JNI_OnLoad(JavaVM* vm,void* reserved)  
  12. {  
  13.     JNIEnv* env = NULL;  
  14.     jint result=-1;  
  15.     if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)  
  16.         return result;  
  17.     jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");  
  18.     /* C */  
  19.     jint r=(*env)->RegisterNatives(env, HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));  
  20.     /* C++ */  
  21.     r=AndroidRuntime::registerNativeMethods(env,"com/example/hellojni/HelloJni",gMethods,NELEM(gMethods));  
[cpp] view plaincopy  
  1.     /*或者env->RegisterNatives(HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));*/  
  2.     if(0 == r)  
  3.         //註冊native函數成功  
  4.     else  
  5.         //註冊native函數失敗  
  6.     return JNI_VERSION_1_4;  
  7. }  
  8.   
  9. void JNI_OnUnload(JavaVM* vm,void* reserved)  
  10. {  
  11.     JNIEnv* env = NULL;  
  12.     if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)  
  13.         return;  
  14.     jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");  
  15.     /* C */  
  16.     jint r=(*env)->UnregisterNatives(env,HelloJniClazz);  
  17.     /* C++ */  
  18.     jint r= env->UnregisterNatives(HelloJniClazz)  
  19.     if(r == 0)  
  20.         //登出native函數成功  
  21.     else  
  22.         //登出native函數失敗  
  23. }  

C和C++中都可以通過JNIEnv的RegisterNatives函數註冊,而C++還提供了AndroidRuntime::registerNativeMethods,AndroidRuntime類的registerNativeMethods方法也可以註冊。

3. 在native中向LogCat輸出調試資訊

在C/C++編譯單元頭部加上

#include <android/log.h>

#define LOG_TAG "自訂一個字串"

log.h聲明了函數int __android_log_print(int prio, const char *tag,  const char *fmt, ...)我們就是用這個函數向LogCat輸出資訊的。

加入了標頭檔後還必須給連結器指定__android_log_print函數所在的庫檔案liblog.so,在Android.mk檔案中加上一行

LOCAL_LDLIBS := -llog

在native函數中可以用如下語句輸出了

__android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"name=%s,age=%d",”盧斌暉”,28);

第一個參數ANDROID_LOG_DEBUG是枚舉常量,它在log.h中定義。

[cpp] view plaincopy  
  1. typedef enum android_LogPriority   
  2. {  
  3.     ANDROID_LOG_UNKNOWN = 0,  
  4.     ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */  
  5.     ANDROID_LOG_VERBOSE,  
  6.     ANDROID_LOG_DEBUG,  
  7.     ANDROID_LOG_INFO,  
  8.     ANDROID_LOG_WARN,  
  9.     ANDROID_LOG_ERROR,  
  10.     ANDROID_LOG_FATAL,  
  11.     ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */  
  12. } android_LogPriority;  

我們可以根據調試資訊的不同類別而選用不同的枚舉常量。

4.關於jclass

jclass代表Java中的java.lang.Class。我們看jclass的定義,下面給出$NDK\platforms\android-5\arch-arm\usr\include\jni.h的部分代碼

[cpp] view plaincopy  
  1. #ifdef __cplusplus  
  2. /*Reference types, in C++*/  
  3. class _jobject {};  
  4. class _jclass : public _jobject {}; /*_jclass繼承_jobject*/  
  5. typedef _jclass*        jclass;  
  6. #else  
  7. /*Reference types, in C.*/  
  8. typedef void*           jobject;  
  9. typedef jobject         jclass;  
  10. #endif  

在C中jclass代表類型void*,在C++中代表類型_jclass*。因此jclass是指標,我們能夠在log中輸出jclass變數值。

    __android_log_print(ANDROID_LOG_DEBUG,"native函數中輸出","地址=%p",jclass變數);

當多個native函數都需要使用同一個JAVA類的jclass變數時,不能夠定義jclass類型全域變數並只對其賦初值一次然後在多次JAVA對native函數調用中使用這個jclass變數。不能企圖以此方式來節約獲得jclass變數的開銷。

每次JAVA調用native都必須重新獲得jclass,上次調用native所得到的jclass在下次調用native時再使用是無效的。下面是C代碼

[cpp] view plaincopy  
  1. static jclass StudentClazz;   //全域變數  
  2. jint JNI_OnLoad(JavaVM* vm,void* reserved)  
  3. {  
  4.     JNIEnv* env = NULL;  
  5.     jint result=-1;  
  6.     if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)  
  7.        return result;  
  8.     StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");    //初始化  
  9.     return JNI_VERSION_1_4;  
  10. }  
  11.   
  12. JNIEXPORT void JNICALL jobjectProcess(JNIEnv *env, jobject instance,jobject student,jobject flag)  
  13. {  
  14.    /*StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");*/  
  15.    __android_log_print(ANDROID_LOG_DEBUG,"在jobjectProcess中輸出","StudentClazz=%p",StudentClazz);  
  16.    nameFieldId=(*env)->GetFieldID(env,StudentClazz,"name","Ljava/lang/String;");  
  17.    jstring name=(jstring)((*env)->GetObjectField(env,student,nameFieldId));  
  18. }  

下面是Activity的代碼

[java] view plaincopy  
  1. static  
  2. {  
  3.     System.loadLibrary("hello-jni");  
  4. }  
  5. public native void jobjectProcess(Student student,Integer flag);  
  6. public static class Student{/*省略的代碼*/}  
  7. protected void onResume()  
  8. {  
  9.     jobjectProcess(new Student(),new Integer(20));  
  10.     super.onResume();  
  11. }  

上面的C代碼在JNI_OnLoad函數中對StudentClazz初始化,在jobjectProcess函數內沒有再對StudentClazz賦值。此時運行程式會出錯並在LogCat輸出如下資訊:

DEBUG/在jobjectProcess中輸出(8494): StudentClazz=0x44c0a8f0

WARN/dalvikvm(8286): JNI WARNING: 0x44c0a8f0 is not a valid JNI reference

WARN/dalvikvm(8286): in Lcom/example/hellojni/HelloJni;.jobjectProcess (Lcom/example/hellojni/HelloJni$Student;Ljava/lang/Integer;)V (GetFieldID)

提示StudentClazz所指向的地址(0x44c0a8f0)不是合法的JNI引用。如果把jobjectProcess函數的第一行注釋解除掉,再次給StudentClazz賦值程式便正常執行。

其實不管在哪個native函數內得到的StudentClazz值都是相同的,但每次native調用還是必須執行一次FindClass重新給StudentClazz賦值。

5.native的char*和JAVA的String相互轉換

首先確保C/C++源檔案的字元編碼是UTF-8與JAVA的class檔案字元編碼保持一致。如果C/C++源碼含有中文,那麼編譯出來的so中的中文字串也儲存為UTF-8編碼,這樣的程式不會產生亂碼。

JNI提供了jstring來引用JAVA的String類型變數,如果native函數需要返回 String或者接受String型別參數就必須使用到jstring。而C/C++用char*引用字串起始地址,當native函數接到jstring後要轉換為char*所指向的字串才能處理。當我們處理完char*所指向的字串又要轉換為jstring才能返回給JAVA代碼。下面給出轉換的方法(下面均是C代碼)。

jstring轉換為char*使用JNIEnv的const char*  GetStringUTFChars(JNIEnv*, jstring, jboolean*)

JNIEnv env=//傳入參數 ;  jstring name=//傳入參數 ;

const char *nameStr=(*env)->GetStringUTFChars(env,name,NULL);

調用完GetStringUTFChars後必須調用JNIEnv的void ReleaseStringUTFChars(JNIEnv*, jstring, const char*)釋放建立的字串。

(*env)-> ReleaseStringUTFChars(env,name, nameStr);

 

char*轉換為jstring使用JNIEnv的jstring  NewStringUTF(JNIEnv*, const char*);

jstring newArgName=(*env)->NewStringUTF(env, nameStr);

調用完NewStringUTF後必須調用JNIEnv的void DeleteLocalRef(JNIEnv*, jobject);釋放建立的jstring。

(*env)-> DeleteLocalRef(env, newArgName);

[轉]JNIEnv解析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.