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
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。
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方法也能夠註冊。
在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;
咱們能夠根據調試信息的不一樣類別而選用不一樣的枚舉常量。
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賦值。
首先確保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);