音视频

Android NDK开发:Native层创建线程

2021-09-20  本文已影响0人  itfitness

目录

函数介绍及演示

●创建线程(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);
}

执行结果效果如下:


案例源码

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

上一篇下一篇

猜你喜欢

热点阅读