Android NDK开发:Native层创建线程
目录
函数介绍及演示
●创建线程(pthread_create)
可以利用这个函数创建线程,参数如下:
int pthread_create(
pthread_t *restrict tidp, //新创建的线程ID的内存地址。
const pthread_attr_t *restrict attr, //线程属性,默认为NULL
void *(*start_rtn)(void *), //新创建的线程从start_rtn函数的地址开始运行
void *restrict arg //默认为NULL。若上述函数需要参数,将参数放入结构中并将地址作为arg传入。
);
具体使用如下(这里我们就只展示Native层的代码了,Activity中也只是加了一个按钮调用这个函数而已):
#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创建的线程";
pthread_create(&pid,NULL,thread_method,(void *)msg);
}
结果如下:
这里要注意的是线程调用的函数一定要加一个返回值,如果不加返回值编译会通过,但是当你执行创建线程的函数时就会崩溃
●结束线程(pthread_exit)
代码中我们在线程执行的函数中加入两行代码,如下:
void* thread_method(void* arg){
LOGE("Native线程","进入线程");
pthread_exit(0);
LOGE("Native线程",(char*)arg);
return NULL;
}
这时我们再运行APP发现在调用了pthread_exit函数后的代码没有执行
●等待上个线程结束(pthread_join)
这个函数的作用主要是等待前一个线程结束,这里我们调整代码,创建两个线程,让线程执行的函数所接收的参数为线程的序号,当执行的是第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);
}
这时我们运行APP结果如下,是第2个线程先打印了,然后第一个线程才打印了
这时我们在创建第1个与第2个线程之间加上pthread_join函数
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_join(pid,NULL);
pthread_create(&pid,NULL,thread_method,(void *)2);
}
这时我们再运行APP结果如下(这里由于在主线程等待会造成UI的卡顿)
●线程分离(pthread_detach)
线程分离简单来讲就是主线程与子线程分离,子线程结束后,资源自动回收,代码如下:
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);
}
扩展补充
●调用Java层回调方法
如何在Native的线程中回调Java的方法呢?这里我们需要做一些调整,因为在Native中创建的每个线程它的JNIEnv都必须是独立的,不能和主线程共享一个JNIEnv,这就需要我们在创建的线程中获取一个当前线程的JNIEnv,而获取线程中的JNIEnv需要我们使用JavaVM来获取,JavaVM我们可以通过JNI_OnLoad函数来获取,这个函数是当System.loadLibrary加载库的时候系统调用的,如下所示:
JavaVM* javaVm;
//会在加载so库的时候自动执行这个方法
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;
}
因为Java中传递过来的对象是个局部变量,接下来我们还需要将Java中传递过来的需要回调的对象设置为全局变量,如下:
jobject globalRunnable;
extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_threaddemo_MainActivity_startThread(
JNIEnv* env,
jobject mainActivity,
jobject runnable) {
//在其他线程中当前函数的局部变量也不能用需要设置为全局变量
globalRunnable = env->NewGlobalRef(runnable);
pthread_t pid;
pthread_create(&pid,NULL,thread_method,(void *)0);
}
接下来我们在线程调用的函数中获取到当前线程的JNIEnv然后执行回调方法,最后也不要忘了,将全局变量删除和将当前线程的JNIEnv分离,代码如下:
void* thread_method(void* arg){
JNIEnv* jniEnv;
//获取一个当前线程的JNIEnv
if(javaVm->AttachCurrentThread(&jniEnv,NULL) == JNI_OK){
jclass jRunnableClazz = jniEnv->GetObjectClass(globalRunnable);
jmethodID jRunMethodId = jniEnv->GetMethodID(jRunnableClazz,"run","()V");
jniEnv->CallVoidMethod(globalRunnable,jRunMethodId);
//用完后需销毁之前设置的那个全局变量
jniEnv->DeleteGlobalRef(globalRunnable);
//分离当前线程的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库的时候自动执行这个方法
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;
//获取一个当前线程的JNIEnv
if(javaVm->AttachCurrentThread(&jniEnv,NULL) == JNI_OK){
jclass jRunnableClazz = jniEnv->GetObjectClass(globalRunnable);
jmethodID jRunMethodId = jniEnv->GetMethodID(jRunnableClazz,"run","()V");
jniEnv->CallVoidMethod(globalRunnable,jRunMethodId);
//用完后需销毁之前设置的那个全局变量
jniEnv->DeleteGlobalRef(globalRunnable);
//分离当前线程的JNIEnv
javaVm->DetachCurrentThread();
}
return NULL;
}
extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_threaddemo_MainActivity_startThread(
JNIEnv* env,
jobject mainActivity,
jobject runnable) {
//在其他线程中当前函数的局部变量也不能用需要设置为全局变量
globalRunnable = env->NewGlobalRef(runnable);
pthread_t pid;
pthread_create(&pid,NULL,thread_method,(void *)0);
}
Activity中的逻辑如下,回调的接口我是直接用的Runnable,我在回调方法中先打印一句话,然后睡眠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层回调","线程执行");
try {
Thread.sleep(3000);
Log.e("JAVA层回调","线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
});
}
/**
* 开启线程
* @return
*/
public native void startThread(Runnable runnable);
}
执行结果效果如下: