[Android JNI] --- JNIEnv和JavaVM

1 JVMEnv

1.1 JNIEnv 是什么

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

#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv; 
#endif

struct JNIEnv_ {
    
    
    const struct JNINativeInterface_ *functions;
#ifdef __cplusplus

    jint GetVersion() {
    
    
        return functions->GetVersion(this);
    }
    jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
                       jsize len) {
    
    
        return functions->DefineClass(this, name, loader, buf, len);
    }
    jclass FindClass(const char *name) {
    
    
        return functions->FindClass(this, name);
    }
    jmethodID FromReflectedMethod(jobject method) {
    
    
        return functions->FromReflectedMethod(this,method);
    }
    jfieldID FromReflectedField(jobject field) {
    
    
        return functions->FromReflectedField(this,field);
    }

    jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) {
    
    
        return functions->ToReflectedMethod(this, cls, methodID, isStatic);
    }

    jclass GetSuperclass(jclass sub) {
    
    
        return functions->GetSuperclass(this, sub);
    }
    //省略其他函数
    //......
}

1.2 如何获取到JNIEnv

对于单线程的情况,我们可以直接通过 JNI 方法传入的参数获取到 JNIEnv,所有的Native函数的第一个参数永远是JNIEnv指针

// 第一个参数就是 JNIEnv
JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj)
{
    
    
    return (*env)->NewStringUTF(env,"Hello from JNI !");
}

对于多线程的情况,首先我们要知道,JNIEnv 是一个线程作用域的变量,不能跨线程传递,不同线程的 JNIEnv 彼此独立。那如何在不同的线程中获取到 JNIEnv :

//定义全局变量
//JavaVM 是一个结构体,用于描述 Java 虚拟机,后面会讲
JavaVM* gJavaVM;

JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj)
{
    
       
    //线程不允许共用env环境变量,但是JavaVM指针是整个jvm共用的,所以可以通过下面的方法保存JavaVM指针,在线程中使用
    env->GetJavaVM(&gJavaVM);
    return (*env)->NewStringUTF(env,"Hello from JNI !");
}

//假设这是一个工具函数,可能被多个线程调用
void util_xxx()
{
    
    
    JNIEnv *env;
    //从全局的JavaVM中获取到环境变量
    gJavaVM->AttachCurrentThread(&env,NULL);

    //就可以使用 JNIEnv 了

    //最后需要做清理操作
    gJavaVM->DetachCurrentThread();
}

1.3 JVIEnv内部函数分类

JNIEnv 中定义的函数可以分为以下几类:

函数名 功能
FindClass 用于获取类
GetObjectClass 通过对象获取这个类
NewGlobalRef 创建 obj 参数所引用对象的新全局引用
NewObject 构造新 Java 对象
NewString 利用 Unicode 字符数组构造新的 java.lang.String 对象
NewStringUTF 利用 UTF-8 字符数组构造新的 java.lang.String 对象
NewArray 创建类型为Type的数组对象
GetField 获取类型为Type的字段
SetField 设置类型为Type的字段的值
GetStaticField 获取类型为Type的static的字段
SetStaticField 设置类型为Type的static的字段的值
CallMethod 调用返回类型为Type的方法
CallStaticMethod 调用返回值类型为Type的static方法

相关的函数不止上面的这些,这些函数的介绍和使用方法,我们可以在开发过程中参考官方文档
https://docs.oracle.com/en/java/javase/11/docs/specs/jni/index.html

2 JavaVM

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

2.1 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;
#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*/
};

2.2 获取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;

方式二:通过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;

3 ava和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*。

猜你喜欢

转载自blog.csdn.net/weixin_42445727/article/details/131561603
今日推荐