在android项目中,需要用到jni相关的东西,在java应用层需要通过jni调用底层C++实现的native接口,同时在底层C++也需要调用java实现的接口,以此来实现异步通信。
我们知道JavaVM是进程相关的,每一个加载native动态库,都会生成一个JavaVM的实例对象,但JNIEnv是与线程相关的,每一个单独的线程都要生成一个JNIEnv的对象实例。
JavaVM接口
第一种方式,在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved)(如果定义了该函数)。第一个参数会传入JavaVM指针。一般都在这个时候保存一个static的JavaVM *jvm,这个jvm指针在进程内是可以放心共享的。
第二种方式,在native code中调用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)可以得到JavaVM指针。
两种情况下,都可以用全局变量,比如JavaVM* g_jvm来保存获得的指针以便在任意上下文中使用。
Android系统是利用第二种方式Invocation interface来创建JVM的。
JNIEnv接口
JNI开发最常见的错误就是滥用了JNIEnv接口。需要强调的是JNIEnv是跟线程相关的。sdk文档中强调了do not cache JNIEnv*,要用的时候在不同线程中再通过JavaVM *jvm的方法来获取与当前线程相关的JNIEnv*。
在native method中,JNIEnv作为第一个参数传入。那么在JNIEnv不作为参数传入的时候,该如何获得它?JNI提供了两个函数:(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL)和(*jvm)->GetEnv(jvm, (void**)env, JNI_VERSION_1_2)。两个函数都利用JavaVM接口获得JNIEnv接口,上面已经讲到如何获得JavaVM接口。当创建的线程需要获取JNIEnv*的时候,最好在刚创建的时候调用一次AttachCurrentThread,最好还是不要缓存这个JNIEnv*,每次需要的时候通过JavaVM*获取,不要忘记线程结束的时候执行DettachCurrentThread。
在我的项目中,我在JNIEXPORT jint JNICALL NI_OnLoad(JavaVM *ajvm, void *reserved)<br />
{jvm=ajvm;
LOGI(TAG,"jni onload, jvm is %p",jvm);
return JNI_VERSION_1_2;
}中保存JavaVM唯一的示例对象。
在java调用C++native接口时,会传入JNIEnv* env和jobject this两个参数,env为与java线程相关的JNIEnv对象。
在C++调用java接口时,因为线程是在C++里面新创建的,就不能使用之前Java传入的JNIEnv对象,因为是不同的线程,就应创建不同的JNIEnv对象。在C++调用java接口,就需要用到AttachCurrentThread这个函数来获取与当前线程相关的JNIEnv对象,调用这个函数将当前线程链接到JavaVM中,该线程就可以使用jni的api来完成调用,一旦本线程结束,就需要调用DettachCurrentThread来释放本线程与JavaVM的链接,如果不调用,则会产生内存泄露。
在项目中,有这样一种情况:Android应用层开启一个线程————————调用底层native接口——————底层native接口回调android应用层接口
在这种情况下,实质上还是在java线程里面,并没有新的C++线程生成,这个JNIEnv对象实质上是通过第二步(调用底层native接口)可以传入进来的,倘若按照正常的流程先AttachCurrentThread和DettachCurrentThread来操作,反而会出错,一般是段错误。
所以如果是在C++线程的函数里调用java函数,是一定要先AttachCurrentThread和DettachCurrentThread,倘若是在java线程,先调用native,native里面再调java函数,是不能DettachCurrentThread的。
还有一种情况,C++调用java函数,java函数里面又生成了新的java线程,貌似也不能DettachCurrentThread,也会报段错误,这个至于是什么原因,目前我也不是很清楚,我猜测是因为在java函数里面又生成了新的子线程。
我们知道JavaVM是进程相关的,每一个加载native动态库,都会生成一个JavaVM的实例对象,但JNIEnv是与线程相关的,每一个单独的线程都要生成一个JNIEnv的对象实例。
JavaVM接口
第一种方式,在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved)(如果定义了该函数)。第一个参数会传入JavaVM指针。一般都在这个时候保存一个static的JavaVM *jvm,这个jvm指针在进程内是可以放心共享的。
第二种方式,在native code中调用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)可以得到JavaVM指针。
两种情况下,都可以用全局变量,比如JavaVM* g_jvm来保存获得的指针以便在任意上下文中使用。
Android系统是利用第二种方式Invocation interface来创建JVM的。
JNIEnv接口
JNI开发最常见的错误就是滥用了JNIEnv接口。需要强调的是JNIEnv是跟线程相关的。sdk文档中强调了do not cache JNIEnv*,要用的时候在不同线程中再通过JavaVM *jvm的方法来获取与当前线程相关的JNIEnv*。
在native method中,JNIEnv作为第一个参数传入。那么在JNIEnv不作为参数传入的时候,该如何获得它?JNI提供了两个函数:(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL)和(*jvm)->GetEnv(jvm, (void**)env, JNI_VERSION_1_2)。两个函数都利用JavaVM接口获得JNIEnv接口,上面已经讲到如何获得JavaVM接口。当创建的线程需要获取JNIEnv*的时候,最好在刚创建的时候调用一次AttachCurrentThread,最好还是不要缓存这个JNIEnv*,每次需要的时候通过JavaVM*获取,不要忘记线程结束的时候执行DettachCurrentThread。
在我的项目中,我在JNIEXPORT jint JNICALL NI_OnLoad(JavaVM *ajvm, void *reserved)<br />
{jvm=ajvm;
LOGI(TAG,"jni onload, jvm is %p",jvm);
return JNI_VERSION_1_2;
}中保存JavaVM唯一的示例对象。
在java调用C++native接口时,会传入JNIEnv* env和jobject this两个参数,env为与java线程相关的JNIEnv对象。
在C++调用java接口时,因为线程是在C++里面新创建的,就不能使用之前Java传入的JNIEnv对象,因为是不同的线程,就应创建不同的JNIEnv对象。在C++调用java接口,就需要用到AttachCurrentThread这个函数来获取与当前线程相关的JNIEnv对象,调用这个函数将当前线程链接到JavaVM中,该线程就可以使用jni的api来完成调用,一旦本线程结束,就需要调用DettachCurrentThread来释放本线程与JavaVM的链接,如果不调用,则会产生内存泄露。
在项目中,有这样一种情况:Android应用层开启一个线程————————调用底层native接口——————底层native接口回调android应用层接口
在这种情况下,实质上还是在java线程里面,并没有新的C++线程生成,这个JNIEnv对象实质上是通过第二步(调用底层native接口)可以传入进来的,倘若按照正常的流程先AttachCurrentThread和DettachCurrentThread来操作,反而会出错,一般是段错误。
所以如果是在C++线程的函数里调用java函数,是一定要先AttachCurrentThread和DettachCurrentThread,倘若是在java线程,先调用native,native里面再调java函数,是不能DettachCurrentThread的。
还有一种情况,C++调用java函数,java函数里面又生成了新的java线程,貌似也不能DettachCurrentThread,也会报段错误,这个至于是什么原因,目前我也不是很清楚,我猜测是因为在java函数里面又生成了新的子线程。