Android NDK開發(fā):Native層創(chuàng)建線程

目錄

函數(shù)介紹及演示

●創(chuàng)建線程(pthread_create)

可以利用這個函數(shù)創(chuàng)建線程忆某,參數(shù)如下:

int pthread_create(
                 pthread_t *restrict tidp,   //新創(chuàng)建的線程ID的內(nèi)存地址蚊伞。
                 const pthread_attr_t *restrict attr,  //線程屬性燕侠,默認(rèn)為NULL
                 void *(*start_rtn)(void *), //新創(chuàng)建的線程從start_rtn函數(shù)的地址開始運(yùn)行
                 void *restrict arg //默認(rèn)為NULL扫腺。若上述函數(shù)需要參數(shù)喉钢,將參數(shù)放入結(jié)構(gòu)中并將地址作為arg傳入。
                  );

具體使用如下(這里我們就只展示Native層的代碼了,Activity中也只是加了一個按鈕調(diào)用這個函數(shù)而已):

#include <jni.h>
#include <string>
#include <pthread.h>
#include "android/log.h"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGD類型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGI類型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGW類型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGE類型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGF類型
void* thread_method(void* arg){
        LOGE("Native線程",(char*)arg);
        return NULL;
}
extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_threaddemo_MainActivity_startThread(
        JNIEnv* env,
        jobject mainActivity) {
    pthread_t pid;
    char* msg = "我是Native創(chuàng)建的線程";
    pthread_create(&pid,NULL,thread_method,(void *)msg);
}

結(jié)果如下:



這里要注意的是線程調(diào)用的函數(shù)一定要加一個返回值宪摧,如果不加返回值編譯會通過,但是當(dāng)你執(zhí)行創(chuàng)建線程的函數(shù)時就會崩潰


●結(jié)束線程(pthread_exit)

代碼中我們在線程執(zhí)行的函數(shù)中加入兩行代碼,如下:

void* thread_method(void* arg){
    LOGE("Native線程","進(jìn)入線程");
    pthread_exit(0);
    LOGE("Native線程",(char*)arg);
    return NULL;
}

這時我們再運(yùn)行APP發(fā)現(xiàn)在調(diào)用了pthread_exit函數(shù)后的代碼沒有執(zhí)行


●等待上個線程結(jié)束(pthread_join)

這個函數(shù)的作用主要是等待前一個線程結(jié)束非剃,這里我們調(diào)整代碼鬓催,創(chuàng)建兩個線程,讓線程執(zhí)行的函數(shù)所接收的參數(shù)為線程的序號,當(dāng)執(zhí)行的是第1個線程的時候我們讓它睡眠1秒,代碼如下:

#include <jni.h>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include "android/log.h"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGD類型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGI類型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGW類型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGE類型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGF類型
void* thread_method(void* arg){
    int num = (int)arg;
    //第一個線程睡眠1秒
    if(num == 1){
        sleep(1);
    }
    LOGE("Native線程","線程:%d",num);
    return NULL;
}
extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_threaddemo_MainActivity_startThread(
        JNIEnv* env,
        jobject mainActivity) {
    pthread_t pid;
    pthread_create(&pid,NULL,thread_method,(void *)1);
    pthread_create(&pid,NULL,thread_method,(void *)2);
}

這時我們運(yùn)行APP結(jié)果如下殊霞,是第2個線程先打印了瘸右,然后第一個線程才打印了



這時我們在創(chuàng)建第1個與第2個線程之間加上pthread_join函數(shù)

extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_threaddemo_MainActivity_startThread(
        JNIEnv* env,
        jobject mainActivity) {
    pthread_t pid;
    pthread_create(&pid,NULL,thread_method,(void *)1);
    //等待上一個線程執(zhí)行完畢
    pthread_join(pid,NULL);
    pthread_create(&pid,NULL,thread_method,(void *)2);
}

這時我們再運(yùn)行APP結(jié)果如下(這里由于在主線程等待會造成UI的卡頓)


●線程分離(pthread_detach)

線程分離簡單來講就是主線程與子線程分離苞俘,子線程結(jié)束后盹沈,資源自動回收,代碼如下:

extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_threaddemo_MainActivity_startThread(
        JNIEnv* env,
        jobject mainActivity) {
    pthread_t pid;
    pthread_create(&pid,NULL,thread_method,(void *)1);
    //線程分離
    pthread_detach(pid);
    pthread_create(&pid,NULL,thread_method,(void *)2);
}

擴(kuò)展補(bǔ)充

●調(diào)用Java層回調(diào)方法

如何在Native的線程中回調(diào)Java的方法呢吃谣?這里我們需要做一些調(diào)整乞封,因?yàn)樵贜ative中創(chuàng)建的每個線程它的JNIEnv都必須是獨(dú)立的,不能和主線程共享一個JNIEnv岗憋,這就需要我們在創(chuàng)建的線程中獲取一個當(dāng)前線程的JNIEnv肃晚,而獲取線程中的JNIEnv需要我們使用JavaVM來獲取,JavaVM我們可以通過JNI_OnLoad函數(shù)來獲取仔戈,這個函數(shù)是當(dāng)System.loadLibrary加載庫的時候系統(tǒng)調(diào)用的关串,如下所示:

JavaVM* javaVm;
//會在加載so庫的時候自動執(zhí)行這個方法
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pVM, void *pVoid){
    javaVm = pVM;
    JNIEnv *env = NULL;
    jint result = -1;
    if (javaVm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }
    result = JNI_VERSION_1_4;
    return result;
}

因?yàn)镴ava中傳遞過來的對象是個局部變量,接下來我們還需要將Java中傳遞過來的需要回調(diào)的對象設(shè)置為全局變量监徘,如下:

jobject globalRunnable;
extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_threaddemo_MainActivity_startThread(
        JNIEnv* env,
        jobject mainActivity,
        jobject runnable) {
    //在其他線程中當(dāng)前函數(shù)的局部變量也不能用需要設(shè)置為全局變量
    globalRunnable = env->NewGlobalRef(runnable);
    
    pthread_t pid;
    pthread_create(&pid,NULL,thread_method,(void *)0);
}

接下來我們在線程調(diào)用的函數(shù)中獲取到當(dāng)前線程的JNIEnv然后執(zhí)行回調(diào)方法晋修,最后也不要忘了,將全局變量刪除和將當(dāng)前線程的JNIEnv分離凰盔,代碼如下:

void* thread_method(void* arg){
    JNIEnv* jniEnv;
    //獲取一個當(dāng)前線程的JNIEnv
    if(javaVm->AttachCurrentThread(&jniEnv,NULL) == JNI_OK){
        jclass jRunnableClazz = jniEnv->GetObjectClass(globalRunnable);
        jmethodID jRunMethodId = jniEnv->GetMethodID(jRunnableClazz,"run","()V");
        jniEnv->CallVoidMethod(globalRunnable,jRunMethodId);
        //用完后需銷毀之前設(shè)置的那個全局變量
        jniEnv->DeleteGlobalRef(globalRunnable);
        //分離當(dāng)前線程的JNIEnv
        javaVm->DetachCurrentThread();
    }
    return NULL;
}

完整的代碼如下:

#include <jni.h>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include "android/log.h"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGD類型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGI類型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGW類型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGE類型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,__VA_ARGS__ ,__VA_ARGS__) // 定義LOGF類型
JavaVM* javaVm;
jobject globalRunnable;

//會在加載so庫的時候自動執(zhí)行這個方法
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pVM, void *pVoid){
    javaVm = pVM;
    JNIEnv *env = NULL;
    jint result = -1;
    if (javaVm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }
    result = JNI_VERSION_1_4;
    return result;
}

void* thread_method(void* arg){
    JNIEnv* jniEnv;
    //獲取一個當(dāng)前線程的JNIEnv
    if(javaVm->AttachCurrentThread(&jniEnv,NULL) == JNI_OK){
        jclass jRunnableClazz = jniEnv->GetObjectClass(globalRunnable);
        jmethodID jRunMethodId = jniEnv->GetMethodID(jRunnableClazz,"run","()V");
        jniEnv->CallVoidMethod(globalRunnable,jRunMethodId);
        //用完后需銷毀之前設(shè)置的那個全局變量
        jniEnv->DeleteGlobalRef(globalRunnable);
        //分離當(dāng)前線程的JNIEnv
        javaVm->DetachCurrentThread();
    }
    return NULL;
}
extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_threaddemo_MainActivity_startThread(
        JNIEnv* env,
        jobject mainActivity,
        jobject runnable) {
    //在其他線程中當(dāng)前函數(shù)的局部變量也不能用需要設(shè)置為全局變量
    globalRunnable = env->NewGlobalRef(runnable);

    pthread_t pid;
    pthread_create(&pid,NULL,thread_method,(void *)0);
}

Activity中的邏輯如下墓卦,回調(diào)的接口我是直接用的Runnable,我在回調(diào)方法中先打印一句話户敬,然后睡眠3秒再打印一句話

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    private Button btStartThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btStartThread = (Button) findViewById(R.id.bt_start_thread);
        btStartThread.setOnClickListener(v->{
            startThread(new Runnable() {
                @Override
                public void run() {
                    Log.e("JAVA層回調(diào)","線程執(zhí)行");
                    try {
                        Thread.sleep(3000);
                        Log.e("JAVA層回調(diào)","線程執(zhí)行完畢");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        });
    }

    /**
     * 開啟線程
     * @return
     */
    public native void startThread(Runnable runnable);
}

執(zhí)行結(jié)果效果如下:


案例源碼

https://gitee.com/itfitness/ndkthread-demo.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末落剪,一起剝皮案震驚了整個濱河市睁本,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌忠怖,老刑警劉巖呢堰,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異脑又,居然都是意外死亡暮胧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門问麸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來往衷,“玉大人,你說我怎么就攤上這事严卖∠幔” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵哮笆,是天一觀的道長来颤。 經(jīng)常有香客問我,道長稠肘,這世上最難降的妖魔是什么福铅? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮项阴,結(jié)果婚禮上滑黔,老公的妹妹穿的比我還像新娘。我一直安慰自己环揽,他們只是感情好略荡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歉胶,像睡著了一般汛兜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上通今,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天粥谬,我揣著相機(jī)與錄音,去河邊找鬼辫塌。 笑死帝嗡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的璃氢。 我是一名探鬼主播哟玷,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了巢寡?” 一聲冷哼從身側(cè)響起喉脖,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抑月,沒想到半個月后树叽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谦絮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年题诵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片层皱。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡性锭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叫胖,到底是詐尸還是另有隱情草冈,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布瓮增,位于F島的核電站怎棱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绷跑。R本人自食惡果不足惜拳恋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砸捏。 院中可真熱鬧谬运,春花似錦、人聲如沸带膜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膝藕。三九已至,卻和暖如春咐扭,著一層夾襖步出監(jiān)牢的瞬間芭挽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工蝗肪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袜爪,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓薛闪,卻偏偏與公主長得像辛馆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容