JNI中AttachCurrentThread和DetachCurrentThread的問題

在《Java與CC++交互JNI編程》中有講過AttachCurrentThreadDetachCurrentThread的使用。

我們知道在jni中我們可以使用pthread或者std::thread創建線程。因為線程并不是從Java環境創建的,所以這時候創建出的線程是沒有JNIEnv的。如果需要使用JNIEnv,可以調用JavaVMAttachCurrentThread將當前線程附加到虛擬機。

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開發的一些建議

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容