標籤:數組 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
- /* JNINativeMethod數組的定義在C和C++中都一樣*/
- static JNINativeMethod gMethods[] = {
- {
- "jobjectProcess",
- "(Lcom/example/hellojni/HelloJni$Student;Ljava/lang/Integer;)V",
- (void*)jobjectProcess
- }
- /*被省略掉的代碼*/
- };
-
- jint JNI_OnLoad(JavaVM* vm,void* reserved)
- {
- JNIEnv* env = NULL;
- jint result=-1;
- if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)
- return result;
- jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");
- /* C */
- jint r=(*env)->RegisterNatives(env, HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));
- /* C++ */
- r=AndroidRuntime::registerNativeMethods(env,"com/example/hellojni/HelloJni",gMethods,NELEM(gMethods));
[cpp] view plaincopy
- /*或者env->RegisterNatives(HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));*/
- if(0 == r)
- //註冊native函數成功
- else
- //註冊native函數失敗
- return JNI_VERSION_1_4;
- }
-
- void JNI_OnUnload(JavaVM* vm,void* reserved)
- {
- JNIEnv* env = NULL;
- if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)
- return;
- jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");
- /* C */
- jint r=(*env)->UnregisterNatives(env,HelloJniClazz);
- /* C++ */
- jint r= env->UnregisterNatives(HelloJniClazz)
- if(r == 0)
- //登出native函數成功
- else
- //登出native函數失敗
- }
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
- typedef enum android_LogPriority
- {
- ANDROID_LOG_UNKNOWN = 0,
- ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
- ANDROID_LOG_VERBOSE,
- ANDROID_LOG_DEBUG,
- ANDROID_LOG_INFO,
- ANDROID_LOG_WARN,
- ANDROID_LOG_ERROR,
- ANDROID_LOG_FATAL,
- ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
- } android_LogPriority;
我們可以根據調試資訊的不同類別而選用不同的枚舉常量。
4.關於jclass
jclass代表Java中的java.lang.Class。我們看jclass的定義,下面給出$NDK\platforms\android-5\arch-arm\usr\include\jni.h的部分代碼
[cpp] view plaincopy
- #ifdef __cplusplus
- /*Reference types, in C++*/
- class _jobject {};
- class _jclass : public _jobject {}; /*_jclass繼承_jobject*/
- typedef _jclass* jclass;
- #else
- /*Reference types, in C.*/
- typedef void* jobject;
- typedef jobject jclass;
- #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
- static jclass StudentClazz; //全域變數
- jint JNI_OnLoad(JavaVM* vm,void* reserved)
- {
- JNIEnv* env = NULL;
- jint result=-1;
- if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)
- return result;
- StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student"); //初始化
- return JNI_VERSION_1_4;
- }
-
- JNIEXPORT void JNICALL jobjectProcess(JNIEnv *env, jobject instance,jobject student,jobject flag)
- {
- /*StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");*/
- __android_log_print(ANDROID_LOG_DEBUG,"在jobjectProcess中輸出","StudentClazz=%p",StudentClazz);
- nameFieldId=(*env)->GetFieldID(env,StudentClazz,"name","Ljava/lang/String;");
- jstring name=(jstring)((*env)->GetObjectField(env,student,nameFieldId));
- }
下面是Activity的代碼
[java] view plaincopy
- static
- {
- System.loadLibrary("hello-jni");
- }
- public native void jobjectProcess(Student student,Integer flag);
- public static class Student{/*省略的代碼*/}
- protected void onResume()
- {
- jobjectProcess(new Student(),new Integer(20));
- super.onResume();
- }
上面的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解析