JNI/NDK入门指南之JavaVM和JNIEnv

JNI/NDK入门指南之JavaVM和JNIEnvJNI NDK 开发指南之 JavaVM 和 JNIEnv 在前面的章节 JNI 数据类型 描述符详解中 我们详解了 JNI 数据类型和描述符的一些概念 那么在今天我们将要熟悉掌握 JNI 的开发中另外两个关键点 JavaVM 和 JniEnv 细说 JavaVMJavaVM 英文全称是 Javavirtualm 用咋中国话来说就是 Java 虚拟机 一个 JVM 中只有一个 JavaVM 对象

      JNI/NDK入门指南之JavaVM和JNIEnv

Android JNI/NDK入门指南目录

JNI/NDK入门指南之正确姿势了解JNI和NDK
JNI/NDK入门指南之JavaVM和JNIEnv
JNI/NDK入门指南之JNI数据类型,描述符详解
JNI/NDK入门指南之jobject和jclass
JNI/NDK入门指南之javah和javap的使用和集成
JNI/NDK入门指南之Eclipse集成NDK开发环境并使用
JNI/NDK入门指南之JNI动/静态注册全分析
JNI/NDK入门指南之JNI字符串处理
JNI/NDK入门指南之JNI访问数组
JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性
JNI/NDK入门指南之C/C++通过JNI访问Java实例方法和类静态方法
JNI/NDK入门指南之JNI异常处理
JNI/NDK入门指南之JNI多线程回调Java方法
JNI/NDK入门指南之正确姿势了解,使用,管理,缓存JNI引用


JNI/NDK入门指南之调用Java构造方法和父类实例方法
JNI/NDK入门指南之C/C++结构体和Java对象转换方式一
JNI/NDK入门指南之C/C++结构体和Java对象转换方式二






























引言

  在前面的章节JNI数据类型,描述符详解中,我们详解了JNI数据类型和描述符的一些概念,那么在今天我们将要熟悉掌握JNI的开发中另外两个关键知识点JavaVMJniEnv


一.细说JavaVM

JavaVM,英文全称是Java virtual machine,用咋中国话来说就是Java虚拟机。一个JVM中只有一个JavaVM对象,这个JavaVM则可以在进程中的各线程间共享的,这个特性在JNI开发中是非常重要的。

1.获取JavaVM虚拟机接口

在JNI的开发中有两种方法可以获取JavaVM,下面来分别介绍一下。
方式一:
在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved)(如果定义了该函数)。第一个参数会传入JavaVM指针。代码如下:




jint JNI_OnLoad(JavaVM * vm, void * reserved){ 
            JNIEnv * env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 
            LOGE(TAG, "ERROR: GetEnv failed\n"); goto bail; } result = JNI_VERSION_1_4; bail: return result; 

方式二:
在Native code中调用JNI_CreateJavaVM(&jvm, (void)&env, &vm_args)可以得到JavaVM指针。我们的Android系统是利用第二种方式来创建art虚拟机的的,具体的创建过程我就不想说了,这个不是本文讲解的重点。对于以上两种获取JavaVM的方式,都可以用全局变量,比如JavaVM* g_jvm来保存获得的指针以便在任意上下文中使用。
在这里插入图片描述



方式三:
通过JNIEnv获取JavaVM,具体参考代码如下:

JNIEXPORT void JNICALL Java_com_xxx_android2native_JniManager_openJni (JNIEnv * env, jobject object) { 
            LOGE(TAG, "Java_com_xxx_android2native_JniManager_openJni"); //注意,直接通过定义全局的JNIEnv和Object变量,在此保存env和object的值是不可以在线程中使用的 //线程不允许共用env环境变量,但是JavaVM指针是整个jvm共用的,所以可以通过下面 //的方法保存JavaVM指针,在线程中使用 env->GetJavaVM(&gJavaVM); //同理,jobject变量也不允许在线程中共用,因此需要创建全局的jobject对象在线程 //中访问该对象 gJavaObj = env->NewGlobalRef(object); gIsThreadStop = 0; 

2.查看JavaVM定义

通过前面的章节我们对JavaVM有了一定的了解,下面让我们看看JNI中对JavaVM的申明,JavaVM申明在jni.h文件里面,这个你一定不会陌生,因为我们在JNI开发中,必定要引入#include

头文件。

C语言中JavaVM声明如下



struct _JNIEnv; struct _JavaVM; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) typedef _JNIEnv JNIEnv; typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv; typedef const struct JNIInvokeInterface* JavaVM;//C语言定义 #endif /* * JNI invocation interface. */ struct JNIInvokeInterface { 
             void* reserved0; void* reserved1; void* reserved2; jint (*DestroyJavaVM)(JavaVM*); jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*); jint (*DetachCurrentThread)(JavaVM*); jint (*GetEnv)(JavaVM*, void**, jint); jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*); }; 

C++中JavaVM声明如下

struct _JNIEnv; struct _JavaVM; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) typedef _JNIEnv JNIEnv; typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv; typedef const struct JNIInvokeInterface* JavaVM; #endif /* * C++ version. */ struct _JavaVM { 
             const struct JNIInvokeInterface* functions; #if defined(__cplusplus) jint DestroyJavaVM() { 
             return functions->DestroyJavaVM(this); } jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) { 
             return functions->AttachCurrentThread(this, p_env, thr_args); } jint DetachCurrentThread() { 
             return functions->DetachCurrentThread(this); } 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*/ }; 

二.细说JNIEnv

JNIEnv,英文全称是Java Native Interface Environment,用咋中国话来说就是Java本地接口环境。在进行JNI编程开发的时候,使用javah生成Native方法对应的Native函数声明,会发现所有的Native函数的第一个参数永远是JNIEnv指针,如下所示:

/* * Class: com_xxx_object2struct_JniTransfer * Method: getJavaBeanFromNative * Signature: ()Lcom/xxx/object2struct/JavaBean; */ JNIEXPORT jobject JNICALL Java_com_xxx_object2struct_JniTransfer_getJavaBeanFromNative (JNIEnv *, jclass); /* * Class: com_xxx_object2struct_JniTransfer * Method: transferJavaBeanToNative * Signature: (Lcom/xxx/object2struct/JavaBean;)V */ JNIEXPORT void JNICALL Java_com_xxx_object2struct_JniTransfer_transferJavaBeanToNative (JNIEnv *, jclass, jobject); 

JNIEnv是提供JNI Native函数的基础环境,线程相关,不同线程的JNIEnv相互独立,并且JNIEnv是一个JNI接口指针,指向了本地方法的一个函数表,该函数表中的每一个成员指向了一个JNI函数,本地方法通过JNI函数来访问JVM中的数据结构,详情如下图:
在这里插入图片描述
通过上面的图示,我们应该更加了解JNIEnv只在当前线程中有效。本地方法不 能将JNIEnv从一个线程传递到另一个线程中。相同的 Java 线程中对本地方法多次调用时,传递给该本地方法的JNIEnv是相同的。但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。




1.查看JNIEnv定义

通过前面的章节我们对JavaVM有了一定的了解,下面让我们看看JNI中对JNIEnv的申明,可以看出JNIEnv是一个包含诸多JNI函数的结构体,JNIEnv申明在jni.h文件里面,这个你一定不会陌生,因为我们在JNI开发中,必定要引入#include

头文件。

C语言中JNIEnv声明如下

struct _JNIEnv; struct _JavaVM; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) typedef _JNIEnv JNIEnv; typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv;//C的定义 typedef const struct JNIInvokeInterface* JavaVM; #endif /* * Table of interface function pointers. */ struct JNINativeInterface { 
                 void* reserved0; void* reserved1; void* reserved2; void* reserved3; jint (*GetVersion)(JNIEnv *); ... } 

在C语言中对JNIEnv下GetVersion()方法使用如下:

 jint version = (*env)->GetVersion(env); 

C++中JNIEnv声明如下

struct _JNIEnv; struct _JavaVM; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) typedef _JNIEnv JNIEnv; typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv;//C的定义 typedef const struct JNIInvokeInterface* JavaVM; #endif /* * C++ object wrapper. * * This is usually overlaid on a C struct whose first element is a * JNINativeInterface*. We rely somewhat on compiler behavior. */ struct _JNIEnv { 
                  /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* functions; #if defined(__cplusplus) jint GetVersion() { 
                  return functions->GetVersion(this); } ... } 

在C++中对JNIEnv下GetVersion()方法使用如下:

 jint version = env->GetVersion(); 

仅从这一部分我们可以看出的是对于JNIEnv在C语言环境和C++语言环境中的实现是不一样的。也就是说我们在C语言和C++语言中对于JNI方法的调用是有区别的。这里我们以GetVersion函数为例说明,其在C和C++中的不同。

2.JNIEnv结构体中JNI函数划分

通过分析前面章节C/C++中JNIEnv结构体的话,不难发现,这个结构体当中包含了几乎有所的JNI函数,大致可以分为如下几类:

函数名 功能
FindClass 该函数用于加载本地定义的类
GetObjectClass 通过对象获取这个类
NewGlobalRef 创建 obj 参数所引用对象的新全局引用
NewObject 构造新 Java 对象
NewString 利用 Unicode 字符数组构造新的 java.lang.String 对象
New

Array
创建类型为Type的数组对象
Get

Field
获取类型为Type的字段
Set

Field
设置类型为Type的字段的值
GetStatic

Field
获取类型为Type的static的字段
SetStatic

Field
设置类型为Type的static的字段的值
Call

Method
调用返回类型为Type的方法
CallStatic

Method
调用返回值类型为Type的static方法

常见的JNI函数还有一些,这里由于篇幅问题就不过多介绍了。这里推荐一个博客JNI学习积累之一 —- 常用函数大全里面有比较详细的描述了,大家可以仔细阅读。当然最好的办法,就是实际使用中慢慢品尝了。

3.获取JNIEnv

如果是在同一个线程中需要使用JNIEnv,这个通过前面的讲解我想读者朋友们一定会脱口而出,使用参数传递,是的这个是可以做到的。但是使用跨线程呢?这个一般会使用到全局引用了,参加如下代码,具体可以参见我的博客Android和C/C++通过Jni实现通信方式一中对于跨线程使用JNIEnv有比较详细的介绍了。

 static void* native_thread_exec(void *arg) { 
                    LOGE(TAG,"nativeThreadExec"); LOGE(TAG,"The pthread id : %d\n", pthread_self()); JNIEnv *env; //从全局的JavaVM中获取到环境变量 gJavaVM->AttachCurrentThread(&env,NULL); //get Java class by classPath //获取Java层对应的类 jclass thiz = env->GetObjectClass(gJavaObj); //get Java method from thiz //获取Java层被回调的函数 jmethodID nativeCallback = env->GetMethodID(thiz,"callByJni","(I)V"); int count = 0; //线程循环 while(!gIsThreadStop) { 
                    sleep(2); //跨线程回调Java层函数 env->CallVoidMethod(gJavaObj,nativeCallback,count++); } gJavaVM->DetachCurrentThread(); LOGE(TAG,"thread stoped"); return ((void *)0); } JNIEXPORT void JNICALL Java_com_xxx_android2native_JniManager_openJni (JNIEnv * env, jobject object) { 
                    LOGE(TAG, "Java_com_xxx_android2native_JniManager_openJni"); //注意,直接通过定义全局的JNIEnv和Object变量,在此保存env和object的值是不可以在线程中使用的 //线程不允许共用env环境变量,但是JavaVM指针是整个jvm共用的,所以可以通过下面 //的方法保存JavaVM指针,在线程中使用 env->GetJavaVM(&gJavaVM); //同理,jobject变量也不允许在线程中共用,因此需要创建全局的jobject对象在线程 //中访问该对象 gJavaObj = env->NewGlobalRef(object); gIsThreadStop = 0; } 

三.Java和Android中JavaVM对象有区别

在Java里,每一个Process可以产生多个JavaVM对象,但是在Android上,每一个Process只有一个art虚拟机对象,也就是在Android进程中是通过有且只有一个虚拟器对象来服务所有Java和C/C++代码 。Java 的dex字节码和C/C++的*.so同时运行ART虚拟机之内,共同使用一个进程空间。之所以可以相互调用,也是因为有ART虚拟机。当Java 代码需要C/C++代码时,在ART虚拟机加载进*.so库时,会先调用JNI_Onload(),此时就会把JAVA VM对象的指针存储于c层jni组件的全局环境中,在Java层调用C层的本地函数时,调用C本地函数的线程必然通过ART虚拟机来调用C层的本地函数,此时,ART虚拟机会为本地的C组件实例化一个JNIEnv指针,该指针指向ART虚拟机的具体的函数列表,当JNI的c组件调用Java层的方法或者属性时,需要通过JNIEnv指针来进行调用。 当本地C/C++想获得当前线程所要使用的JNIEnv时,可以使用ART虚拟机对象的JavaVM* jvm->GetEnv()返回当前线程所在的JNIEnv*。

参考博客:
https://blog.csdn.net/CV_Jason/article/details/
https://www.cnblogs.com/fnlingnzb-learner/p/7366025.html





































版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/221019.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午7:06
下一篇 2026年3月17日 下午7:07


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号