JNIEnv解析

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的部分代码

[cpp]  view plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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);
<think>好的,我现在需要回答用户关于JNIEnv *env的方法及使用示例的问题。首先,我得回顾一下用户提供的引用内容,看看里面有没有相关的信息。引用[2]、[3]、[4]、[5]都提到了JNIEnv和jobject,特别是引用[2]中的示例代码展示了JNIEnv *env作为参数传入本地方法。引用[5]还提到了GetMethodID的使用,这可能和JNIEnv的方法有关。 用户需要详细解析和示例代码,所以我的回答应该结构清晰,分点解释不同的方法,并给出代码示例。根据我的知识,JNIEnv提供了很多函数,比如创建Java对象、调用方法、访问字段等。需要将这些方法分类,比如对象操作、方法调用、字段访问、字符串处理、数组处理等。 首先,要解释JNIEnv的作用,它是Java和本地代码之间的接口,所有JNI操作都通过它进行。然后分点讲解常用的方法,每个方法配以示例代码。例如,FindClass用于查找类,GetMethodID获取方法ID,CallVoidMethod调用方法等。示例代码需要包括Java代码和C/C++的JNI实现,这样用户能清楚如何在实际中使用这些方法。 还需要注意JNIEnv在不同线程中的使用,比如在子线程中需要使用AttachCurrentThread获取JNIEnv指针。这一点可能用户没有提到,但作为常见问题,应该包含进去以提供更全面的信息。 另外,用户提供的引用中有提到GetMethodID的使用,可能用户对方法调用特别感兴趣,所以这部分需要详细说明,包括如何获取方法ID,参数类型的表示,以及实际调用的例子。比如调用静态方法和实例方法的区别,使用不同的CallStatic或Call方法。 最后,生成相关问题,比如异常处理、性能优化、线程安全等,这些都是用户可能进一步想了解的内容。需要确保问题与主题相关,并且引导用户深入探索。 现在需要检查是否有遗漏的重要方法,比如处理字符串和数组的函数,这些在JNI中也很常见。例如NewStringUTF将C字符串转换为jstring,GetArrayLength获取数组长度等。这些都需要涵盖到。 确保代码示例正确,语法正确,比如在C++和C中使用JNIEnv的方式不同,函数调用语法有差异,需要明确指出。例如,C++中可以使用env->Method(),而C中则是(*env)->Method(env, ...)。 总结一下,回答的结构应该是:介绍JNIEnv的作用,分点讲解常用方法及示例,线程注意事项,总结,相关问题。这样用户能够逐步了解JNIEnv的使用,并通过示例代码加深理解。</think>### JNIEnv *env 方法详解及使用示例 JNIEnv(Java Native Interface Environment)是 JNI 编程的核心接口,提供了所有与 Java 环境交互的方法。以下从 **6 个核心维度**解析其常用方法及使用场景,**附代码示例**。 --- #### 一、对象操作 1. **FindClass** 用于查找 Java 类: ```c jclass clazz = env->FindClass("com/example/MyClass"); ``` - 示例:查找 `java.util.Date` 类 ```c jclass dateClass = env->FindClass("java/util/Date"); ``` 2. **NewObject** 创建 Java 对象实例: ```c jobject obj = env->NewObject(clazz, constructorMethodID, args...); ``` --- #### 二、方法调用 1. **GetMethodID** 获取实例方法 ID: ```c jmethodID methodID = env->GetMethodID(clazz, "methodName", "(参数类型)返回值类型"); ``` - 示例:获取 `String.length()` 方法 ```c jmethodID lengthMethod = env->GetMethodID(stringClass, "length", "()I"); ``` 2. **CallVoidMethod** 调用无返回值方法: ```c env->CallVoidMethod(obj, methodID, args...); ``` 3. **CallIntMethod** 调用返回 `int` 的方法: ```c jint result = env->CallIntMethod(obj, methodID, args...); ``` --- #### 三、字段访问 1. **GetFieldID** 获取字段 ID: ```c jfieldID fieldID = env->GetFieldID(clazz, "fieldName", "字段类型"); ``` - 示例:获取 `public int value` 字段 ```c jfieldID valueField = env->GetFieldID(clazz, "value", "I"); ``` 2. **SetIntField** 修改 `int` 类型字段值: ```c env->SetIntField(obj, fieldID, 100); ``` --- #### 四、字符串处理 1. **NewStringUTF** 将 C 字符串转为 Java 的 `jstring`: ```c jstring jstr = env->NewStringUTF("Hello JNI"); ``` 2. **GetStringUTFChars** 获取 Java 字符串的 C 字符指针: ```c const char* cstr = env->GetStringUTFChars(jstr, NULL); ``` --- #### 五、数组操作 1. **GetArrayLength** 获取数组长度: ```c jsize length = env->GetArrayLength(jarray); ``` 2. **GetIntArrayElements** 获取 `int[]` 数组指针: ```c jint* arr = env->GetIntArrayElements(jintArray, NULL); ``` --- #### 六、线程注意事项 在子线程中使用 JNIEnv 时,需通过 `AttachCurrentThread` 获取环境指针: ```c JavaVM* vm; // 全局保存的 JavaVM 指针 JNIEnv* env; vm->AttachCurrentThread(&env, NULL); // 执行 JNI 操作 vm->DetachCurrentThread(); ``` --- #### 完整示例代码 **Java 类定义**: ```java public class JNIDemo { public native void modifyObject(); } ``` **C++ 实现**: ```c++ #include <jni.h> extern "C" JNIEXPORT void JNICALL Java_JNIDemo_modifyObject(JNIEnv *env, jobject obj) { // 1. 获取类信息 jclass clazz = env->GetObjectClass(obj); // 2. 获取字段 ID jfieldID fieldID = env->GetFieldID(clazz, "value", "I"); // 3. 修改字段值 env->SetIntField(obj, fieldID, 42); // 4. 调用方法 jmethodID methodID = env->GetMethodID(clazz, "print", "()V"); env->CallVoidMethod(obj, methodID); } ``` --- ### 总结 JNIEnv 是 Java 与本地代码交互的桥梁,通过其方法可实现: - 对象创建与方法调用(`FindClass`/`CallMethod`) - 字段读写(`GetFieldID`/`SetIntField`) - 字符串与数组处理(`NewStringUTF`/`GetArrayLength`) 线程安全需通过 `AttachCurrentThread` 确保[^2][^5]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值