在《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);
}