转载

JNI编程如何巧妙获取JNIEnv

这里穿插一篇Java JNI相关的知识点,总结一下自己平时工作心得,相信会对做JNI编程的同学有所帮助。

背景:

JNI编程如何巧妙获取JNIEnv

作者目前在做Android项目,但大多数逻辑都会在Native层实现,不可避免的需要在Native层使用C++去调用Java的方法,但是在Native层调用Java方法就需要JNIEnv指针,那如何方便的获取JNIEnv的指针呢?

分析:

如下代码:

JNIEXPORT void Java_com_Activity_testEnv( JNIEnv* env, jobject obj) {

g_obj = env->NewGlobalRef(obj);

}

我们平时可能都见过这种代码,Java层定义了Native的testEnv方法,在Native层就有一个相应的方法与之对应,同时带有JNIEnv*和jobject的参数(在static的native方法中会是jclass类型的参数),但是如果这种代码呢?

JNIEXPORT void Java_com_Activity_testEnv(JNIEnv* env, jobject obj) {

g_obj = env->NewGlobalRef(obj);

func1(env);

func2(env);

func3(env);

func4(env);

func5(env);

func6(env);

func7(env);

func8(env);

func9(env);

}

定义的每个函数都需要将JNIEnv*作为参数传递,如果函数内还有很多嵌套,这种方式简直就是灾难,都需要将JNIEnv *作为参数传递?是不是很麻烦?你可能有这样的想法,我们把env存到本地不就可以了吗,答案是不可以,因为每一个Java线程都会有一个对应的env,我们在Native层无法感知到是哪一个Java线程,保存的env可能当时有效,换一个线程就会失效,而且Native层的函数也可以是从Native线程(即pthread创建的线程)调用,与Java线程没有关联,保存的env必然是失效的,那怎么办呢?

解决 使用JavaVM,这里先介绍下JNIEnv和JavaVM的概念。

JavaVM :Java虚拟机在Native层的代表,在Android中一个进程只有一个JavaVM,所有的线程共用一个JavaVM。

JNIEnv :Java调用Native语言的环境,是一个封装了几乎所有JNI方法的指针,每一个Java线程都有一个对应的JNIEnv,JNIEnv只在当前线程可用,不能跨线程使用,不同线程的JNIEnv彼此独立。在Native环境中创建的线程,如果需要调用JNI方法,必须要调用AttachCurrentThread()与JVM进行关联,使用后也需要调用DetachCurrentThread()来解除关联。

小总结:

在Android进程中,在Native层,通过任何一个可用的JNIEnv都可以获取到整个进程唯一的JavaVM,在任何线程中都可以通过JavaVM获取当前线程可用的JNIEnv,如果是Native线程还需要额外与JVM进行关联。

到这里大家可能都清楚了,只要能够得到JavaVM就可以解决JNIEnv的问题,那如何获取JavaVM呢?

如何获取JavaVM?

这里只介绍Android中常见的获取JavaVM的方法。

方法一: 在Android中调用Native方法前通常都会先加载Native的动态链接库,通常都是使用这种方法:

System.loadLibrary(xxx);

这个方法调用后Native层会自动调用JNI_OnLoad方法:

JavaVM *global_jvm;

jint JNI_OnLoad(JavaVM* vm, void* reserved) {

global_jvm = vm;

}

这样JavaVM就已经获取到啦,将其保存起来即可。

方法二: 通过JNIEnv获取JavaVM,在程序的最开始写一个类似于初始化功能的函数,传到Native层一个可用的JNIEnv,之后就可以获取到JavaVM。

JavaVM *global_jvm;

void get_jvm(JNIEnv *env) {

env->GetJavaVM(&global_jvm);

}

如何通过JavaVM获取JNIEnv?

直接看代码:

JNIEnv *get_env(int *attach) {

if (global_jvm == NULL) return NULL;


*attach = 0;

JNIEnv *jni_env = NULL;


int status = global_jvm->GetEnv((void **)&jni_env, JNI_VERSION_1_6);


if (status == JNI_EDETACHED || jni_env == NULL) {

status = global_jvm->AttachCurrentThread(&jni_env, NULL);

if (status < 0) {

jni_env = NULL;

} else {

*attach = 1;

}

}

return jni_env;

}

void del_env() {

return global_jvm->DetachCurrentThread();

}

通过前面保存的JavaVM就可以获取到JNIEnv,注意get_env函数有一个参数attach,attach是一个出参,这个参数返回1时,代表当前线程是Native线程,使用完后需要调用del_env()断开与JVM的链接。

使用方法如下:

jobject new_global_object(jobject obj) {

int attach = 0;

JNIEnv *env = get_env(&attach);

jobject ret = env->NewGlobalRef(obj);

if (attach == 1) {

del_env();

}

return ret;

}

使用这种方式后,我们再也不用被如何获取JNIEnv的问题困扰啦。

参考资料

https://blog.csdn.net/afei__/article/details/80986203

https://www.cnblogs.com/fnlingnzb-learner/p/7366025.html

https://www.cnblogs.com/MMLoveMeMM/archive/2014/07/15/3846448.html

技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

JNI编程如何巧妙获取JNIEnv

喜欢就点个 「在看」  ▽

原文  http://mp.weixin.qq.com/s?__biz=MzA4MjU1MDk3Ng==&mid=2451527556&idx=2&sn=81efb70af4955f08358b75dc5a3387f9
正文到此结束
Loading...