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);

相关文章
相关标签/搜索