前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JNI编程如何巧妙获取JNIEnv

JNI编程如何巧妙获取JNIEnv

作者头像
音视频开发进阶
发布2020-06-24 16:40:12
4.7K1
发布2020-06-24 16:40:12
举报
文章被收录于专栏:进击的多媒体开发

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

背景

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

分析

如下代码:

代码语言:javascript
复制
JNIEXPORT void Java_com_Activity_testEnv( JNIEnv* env, jobject obj) {
   g_obj = env->NewGlobalRef(obj);
}

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

代码语言:javascript
复制
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的动态链接库,通常都是使用这种方法:

代码语言:javascript
复制
System.loadLibrary(xxx);

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

代码语言:javascript
复制
JavaVM *global_jvm;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    global_jvm = vm;
}

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

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

代码语言:javascript
复制
JavaVM *global_jvm;
void get_jvm(JNIEnv *env) {
   env->GetJavaVM(&global_jvm);
}

如何通过JavaVM获取JNIEnv?

直接看代码:

代码语言:javascript
复制
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;
}
代码语言:javascript
复制
void del_env() {
   return global_jvm->DetachCurrentThread();
}

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

使用方法如下:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 音视频开发进阶 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何获取JavaVM?
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档