美文网首页音视频
Android NDK开发:Native层创建线程

Android NDK开发:Native层创建线程

作者: itfitness | 来源:发表于2021-09-20 17:59 被阅读0次

    目录

    函数介绍及演示

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

    相关文章

      网友评论

        本文标题:Android NDK开发:Native层创建线程

        本文链接:https://www.haomeiwen.com/subject/totvgltx.html