JNIEnv解析

1.關於JNIEnv和JavaVM

 JNIEnv是一個與線程相關的變量,不一樣線程的JNIEnv彼此獨立。JavaVM是虛擬機在JNI層的表明,在一個虛擬機進程中只有一個 JavaVM,所以該進程的全部線程均可以使用這個JavaVM。當後臺線程須要調用JNI native時,在native庫中使用全局變量保存JavaVM尤其重要,這樣使得後臺線程能經過JavaVM得到JNIEnv。java

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

在C中:數組

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

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

在C++中:編碼

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

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

上面這兩者的區別是,在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的部分代碼指針

[cpp] view plaincopy

  1. struct _JNIEnv;  

  2.   

  3. struct _JavaVM;  

  4.   

  5. #if defined(__cplusplus)  

  6.   

  7. typedef _JNIEnv JNIEnv;                                 //C++使用這個類型  

  8.   

  9. typedef _JavaVM JavaVM;                                 //C++使用這個類型  

  10.   

  11. #else  

  12.   

  13. typedef const struct JNINativeInterface* JNIEnv;        //C使用這個類型  

  14.   

  15. typedef const struct JNIInvokeInterface* JavaVM;        //C使用這個類型  

  16.   

  17. #endif  

  18.   

  19. struct JNINativeInterface  

  20.   

  21. {  

  22.   

  23.     /****省略了的代碼****/  

  24.   

  25.     jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);  

  26.   

  27.     /****省略了的代碼****/  

  28.   

  29.     jobject     (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);  

  30.   

  31.     /****省略了的代碼****/  

  32.   

  33. };  

  34.   

  35. struct _JNIEnv  

  36. {  

  37.     const struct JNINativeInterface* functions;  

  38.     #if defined(__cplusplus)  

  39.     /****省略了的代碼****/  

  40.     jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)  

  41.     { return functions->GetMethodID(this, clazz, name, sig); }  

  42.     /****省略了的代碼****/  

  43.     jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)  

  44.     { return functions->GetStaticObjectField(this, clazz, fieldID); }  

  45.     /****省略了的代碼****/  

  46.     #endif /*__cplusplus*/  

  47. };  

  48.   

  49. struct JNIInvokeInterface  

  50. {  

  51.      /****省略了的代碼****/  

  52.     jint (*GetEnv)(JavaVM*, void**, jint);  

  53.     jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);  

  54. };  

  55.   

  56. struct _JavaVM  

  57. {  

  58.     const struct JNIInvokeInterface* functions;  

  59.     #if defined(__cplusplus)  

  60.     /****省略了的代碼****/  

  61.     jint GetEnv(void** env, jint version)  

  62.     { return functions->GetEnv(this, env, version); }  

  63.     jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)  

  64.     { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }  

  65.     #endif /*__cplusplus*/  

  66. };  

假如咱們用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);

相關文章
相關標籤/搜索