在《Java与CC++交互JNI编程》中有讲过AttachCurrentThread
和DetachCurrentThread
的使用。
我们知道在jni中我们可以使用pthread
或者std::thread
创建线程。因为线程并不是从Java
环境创建的,所以这时候创建出的线程是没有JNIEnv
的。如果需要使用JNIEnv
,可以调用JavaVM
的AttachCurrentThread
将当前线程附加到虚拟机。
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args);
AttachCurrentThread
可以调用多次,第一次会附加当前线程到虚拟机并返回JNIEnv
,之后再调用的时候因为当前线程已经被附加到虚拟机上了,所以就不需要重复附加了,仅仅只返回JNIEnv
即可,作用相当于GetEnv
。
需要注意的是,在线程退出之前我们必须要调用DetachCurrentThread
从虚拟机分离当前线程,,不然会造成内存泄露,线程也不会退出。对于native
层创建出来的线程,在调用AttachCurrentThread
的时候会创建本地引用表,在调用DetachCurrentThread
的时候会释放本地引用表。
但是一般我们并不会频繁的调用AttachCurrentThread/DetachCurrentThread
,这样效率很低。一般我们在线程的入口函数调用一次AttachCurrentThread
,在线程入口函数退出之前调用一次DetachCurrentThread
即可。所以一定要手动释放每个本地引用。不然本地引用越来越多,很容易超出最大限制。
下面这个例子很好的演示了AttachCurrentThread/DetachCurrentThread
的用法:
#include <jni.h>
#include <pthread.h>
#include <android/log.h>
extern JavaVM *g_vm;
JNIEnv* getEnv();
void* __start_routine(void*) {
JNIEnv *env1, *env2, *env3, *env4, *env5;
int ret;
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_4;
args.name = "pthread-test";//给线程起个名字吧,这样在调试或者崩溃的时候能显示出名字,而不是thead-1,thread-2这样的名字。
args.group = NULL;//java.lang.ThreadGroup的全局引用,作用你懂的。
//在调用AttachCurrentThread以前,是没有java环境的,所以GetEnv返回的JNIEnv是NULL
g_vm->GetEnv((void**)&env1,JNI_VERSION_1_4);
__android_log_print(ANDROID_LOG_DEBUG, "MD_DEBUG", "before AttachCurrentThread env is:%p", env1);
//调用AttachCurrentThread,将当前线程附加到虚拟机,附加成功后,将会返回JNIEnv
ret = g_vm->AttachCurrentThread(&env2, &args);
__android_log_print(ANDROID_LOG_DEBUG, "MD_DEBUG", "do AttachCurrentThread env is:%p, ret=%d", env2, ret);
//在调用AttachCurrentThread以后,GetEnv返回了正确的JNIEnv
g_vm->GetEnv((void**)&env3,JNI_VERSION_1_4);
__android_log_print(ANDROID_LOG_DEBUG, "MD_DEBUG", "after AttachCurrentThread env is:%p", env3);
//再次调用AttachCurrentThread,直接返回JNIEnv,作用相当于GetEnv
ret = g_vm->AttachCurrentThread(&env4, NULL);
__android_log_print(ANDROID_LOG_DEBUG, "MD_DEBUG", "retry AttachCurrentThread env is:%p, ret=%d", env4, ret);
//从虚拟机分离线程
g_vm->DetachCurrentThread();
//在调用DetachCurrentThread以后,GetEnv返回NULL
g_vm->GetEnv((void**)&env5,JNI_VERSION_1_4);
__android_log_print(ANDROID_LOG_DEBUG, "MD_DEBUG", "after DetachCurrentThread env is:%p", env5);
return NULL;
}
extern "C" JNIEXPORT void Java_com_example_threadtest_PThread_start(JNIEnv *env, jclass clazz) {
pthread_t thread;
pthread_create(&thread, NULL, __start_routine, NULL);
}
深入浅出Android NDK之在jni中使用线程
关于JNI开发的一些建议