目錄
函數(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é)果效果如下: