600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > JNI/NDK入门指南之JavaVM和JNIEnv

JNI/NDK入门指南之JavaVM和JNIEnv

时间:2023-07-22 17:22:22

相关推荐

JNI/NDK入门指南之JavaVM和JNIEnv

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 <jni.h>头文件。

C语言中JavaVM声明如下

struct _JNIEnv;struct _JavaVM;typedef const struct JNINativeInterface* C_JNIEnv;#if defined(__cplusplus)typedef _JNIEnv JNIEnv;typedef _JavaVM JavaVM;#elsetypedef 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;#elsetypedef 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 <jni.h>头文件。

C语言中JNIEnv声明如下

struct _JNIEnv;struct _JavaVM;typedef const struct JNINativeInterface* C_JNIEnv;#if defined(__cplusplus)typedef _JNIEnv JNIEnv;typedef _JavaVM JavaVM;#elsetypedef 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;#elsetypedef 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函数,大致可以分为如下几类:

常见的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*。

参考博客:

/CV_Jason/article/details/80026265

/fnlingnzb-learner/p/7366025.html

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。