这里穿插一篇Java JNI相关的知识点,总结一下自己平时工作心得,相信会对做JNI编程的同学有所帮助。
背景:
作者目前在做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呢?
这里只介绍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