Android JNI 中的線程操做

學習一下如何在 Native 代碼中使用線程。java

Native 中支持的線程標準是 POSIX 線程,它定義了一套建立和操做線程的 API 。android

咱們能夠在 Native 代碼中使用 POSIX 線程,就至關於使用一個庫同樣,首先須要包含這個庫的頭文件:git

#include <pthread.h>
複製代碼

這個頭文件中定義了不少和線程相關的函數,這裏就暫時使用到了其中部份內容。github

建立線程

POSIX 建立線程的函數以下:微信

int pthread_create(
	pthread_t* __pthread_ptr, 
	pthread_attr_t const* __attr, 
	void* (*__start_routine)(void*), 
	void* arg);
複製代碼

它的參數對應以下:函數

  • __pthread_ptr 爲指向 pthread_t 類型變量的指針,用它表明返回線程的句柄。post

  • __attr 爲指向 pthread_attr_t 結構的指針,能夠經過該結構來指定新線程的一些屬性,好比棧大小、調度優先級等,具體看 pthread_attr_t 結構的內容。若是沒有特殊要求,可以使用默認值,把該變量取值爲 NULL 。學習

  • 第三個參數爲該線程啓動程序的函數指針,也就是線程啓動時要執行的那個方法,相似於 Java Runnable 中的 run 方法,它的函數簽名格式以下:spa

void* start_routine(void* args) 複製代碼

啓動程序將線程參數當作 void 指針,返回 void 指針類型結果。線程

  • 第四個參數爲線程啓動程序的參數,也就是函數的參數,若是不須要傳遞參數,它能夠爲 NULL 。

pthread_create 函數若是執行成功了則返回 0 ,若是返回其餘錯誤代碼。

接下來,咱們能夠體驗一下 pthread_create 方法建立線程。

首先,建立線程啓動時運行的函數:

void *printThreadHello(void *);
void *printThreadHello(void *) {
    LOGD("hello thread");
    // 切記要有返回值
    return NULL;
}
複製代碼

要注意線程啓動函數是要有返回值的,沒有返回值就直接崩潰了。

另外這個函數一旦 pthread_create 調用了,它就會當即執行。

接下來建立線程:

JNIEXPORT void JNICALL Java_com_glumes_cppso_jnioperations_ThreadOps_createNativeThread(JNIEnv *, jobject) {
    pthread_t handles; // 線程句柄
    int result = pthread_create(&handles, NULL, printThreadHello, NULL);
    if (result != 0) {
        LOGD("create thread failed");
    } else {
        LOGD("create thread success");
    }
}
複製代碼

因爲沒有給該線程設置屬性,而且線程運行函數也不須要參數,就都直接設置爲了 NULL,那麼上面那段程序就能夠執行了,而且 printThreadHello 函數是運行在新的線程中的。

將線程附着在 Java 虛擬機上

在上面的線程啓動函數中,只是簡單的執行了打印 log 的操做,若是想要執行和 Java 相關的操做,好比從 JNI 調用 Java 的函數等等,那就須要用到 Java 虛擬機環境了,也就是用到 JNIEnv 指針,畢竟全部的調用函數都是以它開頭的。

pthread_create 建立的線程是一個 C++ 中的線程,虛擬機並不能識別它們,爲了和 Java 空間交互,須要先把 POSIX 線程附着到 Java 虛擬機上,而後就能夠得到當前線程的 JNIEnv 指針,由於 JNIEnv 指針只是在當前線程中有效。

經過 AttachCurrentThread 方法能夠將當前線程附着到 Java 虛擬機上,而且能夠得到 JNIEnv 指針。

AttachCurrentThread 方法是由 JavaVM 指針調用的,它表明的是 Java 虛擬機接口指針,能夠在 JNI_OnLoad 加載時來得到,經過全局變量保存起來

static JavaVM *gVm = NULL;
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    gVm = vm;
    return JNI_VERSION_1_6;
}
複製代碼

當經過 AttachCurrentThread 方法將線程附着當 Java 虛擬機上後,還須要將該線程從 Java 虛擬機上分離,經過 DetachCurrentThread 方法,這兩個方法是要同時使用的,不然會帶來 BUG 。

具體使用以下:

首先在 Java 中定義在 C++ 線程中回調的方法,主要就是打印線程名字:

private void printThreadName() {
        LogUtil.Companion.d("print thread name current thread name is " + Thread.currentThread().getName());
            Thread.sleep(5000);
    }
複製代碼

而後定義 Native 線程中運行的方法:

void *run(void *);
void *run(void *args) {
    JNIEnv *env = NULL;
    // 將當前線程添加到 Java 虛擬機上
    // 假設有參數傳遞
    ThreadRunArgs *threadRunArgs = (ThreadRunArgs *) args;
    if (gVm->AttachCurrentThread(&env, NULL) == 0) {
    // 回調 Java 的方法,打印當前線程 id ,發現不是主線程就對了
        env->CallVoidMethod(gObj, printThreadName);
        // 從 Java 虛擬機上分離當前線程
        gVm->DetachCurrentThread();  
    }
    return (void *) threadRunArgs->result;
}
複製代碼

最後建立線程並運行方法:

// 建立傳遞的參數
	    ThreadRunArgs *threadRunArgs = new ThreadRunArgs();
        threadRunArgs->id = i;
        threadRunArgs->result = i * i;
        // 運行線程
        int result = pthread_create(&handles, NULL, run, (void *) threadRunArgs);
複製代碼

經過這樣的調用,就能夠在 Native 線程中調用 Java 相關的函數了。

等待線程返回結果

前面提到在線程運行函數中必需要有返回值,最開始只是返回了一個空指針 NULL ,而且在某個方法裏面開啓了新線程,新線程運行後,該方法也就當即返回退出,執行完了。

如今,還能夠在該方法裏等待線程執行完畢後,拿到線程執行完的結果以後再推出。

經過 pthread_join 方法能夠等待線程終止。

int pthread_join(pthread_t __pthread, void** __return_value_ptr);
複製代碼

其中:

  • __pthread 表明建立線程的句柄
  • __return_value_ptr 表明線程運行函數返回的結果

使用以下:

for (int i = 0; i < num; ++i) {
        pthread_t pthread;
        // 建立線程,
        int result = pthread_create(&handles[i], NULL, run, (void *) threadRunArgs);
        }
    }
    for (int i = 0; i < num; ++i) {
        void *result = NULL; // 線程執行返回結果
        // 等待線程執行結束
        if (pthread_join(handles[i], &result) != 0) {
            throwByName(env, runtimeException, "Unable to join thread");
        } else {
	        LOGD("return value is %d",result);
        }
    }
複製代碼

若是 pthread_join 返回爲 0 表明執行成功,非 0 則執行失敗。

具體的代碼示例能夠參考個人 Github 項目,歡迎 Star 。

github.com/glumes/Andr…

JNI 系列文章:

  1. Android JNI 中的引用管理
  2. Android JNI 調用時的異常處理
  3. Android JNI 基本操做

歡迎關注微信公衆號:【紙上淺談】,得到最新文章推送~~~

掃碼關注
相關文章
相關標籤/搜索