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

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » JNI编程如何巧妙获取JNIEnv

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址