Android JNI學(xué)習(xí)-線程操作

Android Native中支持的線程標(biāo)準(zhǔn)是 POSIX 線程。POSIX 線程也被簡稱為Pthreads,是一個線程的POSIX 標(biāo)準(zhǔn),它為創(chuàng)建和處理線程定義了一個通用的API。

POSIX Thread 的Android實(shí)現(xiàn)是Bionic標(biāo)準(zhǔn)庫的一部分,在編譯的時候不需要鏈接任何其他的庫,只需要包含一個頭文件。

#include <pthread.h>

創(chuàng)建線程

線程創(chuàng)建函數(shù):

int pthread_create(
    pthread_t* thread, 
    pthread_attr_t const* attr, 
    void* (*start_routine)(void*), 
    void* arg);

  • thread:指向 pthread_t 類型變量的指針,用它代表返回線程的句柄

  • attr:指向 pthread_attr_t 結(jié)構(gòu)的指針形式存在的新線程屬性,可以通過該結(jié)構(gòu)來指定新線程的一些屬性,比如棧大小、調(diào)度優(yōu)先級等,具體看 pthread_attr_t 結(jié)構(gòu)的內(nèi)容。如果沒有特殊要求,可使用默認(rèn)值,把該變量取值為 NULL 。

  • 第三個參數(shù)是指向啟動函數(shù)的函數(shù)指針,它的函數(shù)簽名格式如下:

    void* start_routine(void* args)
    

    啟動程序?qū)⒕€程參數(shù)看成 void 指針,返回 void 指針類型結(jié)果。

  • 線程啟動程序的參數(shù),也就是函數(shù)的參數(shù),如果不需要傳遞參數(shù),它可以為 NULL 。

pthread_create 函數(shù)如果執(zhí)行成功了則返回 0 ,如果返回其他錯誤代碼。

void sayHello(void *){
    LOGE("say %s","hello");
}

JNIEXPORT jint JNICALL Java_com_david_JNIController_sayhello
        (JNIEnv *jniEnv, jobject instance) {
    pthread_t handles; // 線程句柄
    int ret = pthread_create(&handles, NULL, sayHello, NULL);
    if (ret != 0) {
        LOGE("create thread failed");
    } else {
        LOGD("create thread success");
    }
}

調(diào)用函數(shù)就可以在線程執(zhí)行打印say hello了。

附著在Java虛擬機(jī)上

創(chuàng)建了線程后,只能做一些簡單的Native操作,如果想要對Java層做一些操作就不行了,因?yàn)樗鼪]有Java虛擬機(jī)環(huán)境,這個時候?yàn)榱撕蚃ava空間進(jìn)行交互,就要把POSIX 線程附著在Java虛擬機(jī)上,然后就可以獲得當(dāng)前線程的 JNIEnv 指針了。

通過 AttachCurrentThread 方法可以將當(dāng)前線程附著到 Java 虛擬機(jī)上,并且可以獲得 JNIEnv 指針。而AttachCurrentThread 方法是由 JavaVM 指針調(diào)用的,可以在JNI_OnLoad函數(shù)中將JavaVM 保存為全局變量。

static JavaVM *jVm = NULL;
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    jVm = vm;
    return JNI_VERSION_1_6;
}

如上一個例子,我們想要在sayHello函數(shù)中調(diào)用一個Java層的函數(shù)javaSayHello()

private void javaSayHello() {
    Log.e(TAG,"java say hello");
}
void sayHello(void *){
    LOGE("say %s","hello");
     JNIEnv *env = NULL;
    // 將當(dāng)前線程添加到 Java 虛擬機(jī)上
    if (jVm->AttachCurrentThread(&env, NULL) == 0) {
        ......
        env->CallVoidMethod(Obj, javaSayHello);
        // 從 Java 虛擬機(jī)上分離當(dāng)前線程
        jVm->DetachCurrentThread();  
    }
    return NULL;
}

這樣就在 Native 線程中調(diào)用 Java 相關(guān)的函數(shù)了。

等待線程返回結(jié)果

前面提到的方法是新線程運(yùn)行后,該方法也就立即返回退出,執(zhí)行完了。我們也可以通過另一個函數(shù)可以在等待線程執(zhí)行完畢后,拿到線程執(zhí)行完的結(jié)果之后再退出。

int pthread_join(pthread_t pthread, void** ret_value);
  • pthread 代表創(chuàng)建線程的句柄
  • ret_value代表線程運(yùn)行函數(shù)返回的結(jié)果
    pthread_t* handles = new pthread_t[10];
    
    for (int i = 0; i < 10; ++i) {
        pthread_t pthread;
        // 創(chuàng)建線程,
        int result = pthread_create(&handles[i], NULL, run, NULL;
        }
    }
    for (int i = 0; i < 10; ++i) {
        void *result = NULL; // 線程執(zhí)行返回結(jié)果
        // 等待線程執(zhí)行結(jié)束
        if (pthread_join(handles[i], &result) != 0) {
            env->ThrowNew(env, runtimeException, "Unable to join thread");
        } else {
            LOGD("return value is %d",result);
        }
    }

pthread_join 返回為 0 代表執(zhí)行成功,非 0 則執(zhí)行失敗。

同步代碼塊

在Java中,JDK為我們提供了synchronized來處理多線程同步代碼塊。

 synchronized (object.class) {
        // 業(yè)務(wù)處理
    }

本地代碼中,JNI提供了兩個函數(shù)來完成上面的同步:

(1)MonitorEnter:進(jìn)入同步代碼塊

(2)MonitorExit:退出同步代碼塊

if(env->MonitorEnter(obj)!= JNI_OK){
    // 錯誤處理
}

// 同步代碼塊

// 出現(xiàn)錯誤釋放代碼塊
if(env->ExceptionCheck()){
    if(env->MonitorExit(obj)!= JNI_OK);
       return;
}

if(env->MonitorExit(obj)!= JNI_OK){
    // 錯誤處理
}

可以發(fā)現(xiàn)在本地代碼中處理同步代碼塊要比Java中復(fù)雜的多,所以,盡量用Java來做同步吧,把與同步相關(guān)的代碼都移到Java中去。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容