NDK多线程的使用与问题

作者: zhang_pan | 来源:发表于2018-03-12 18:09 被阅读178次

    1. 前言

    前面已经介绍了POSIX线程原语,如果有不清楚的,可以查看之前的博客Linux入门之POSIX线程原语,下面我将在NDK开发中用POSIX线程开启线程执行异步操作。

    2. 多线程

    这里的需求就是在子线程中调用UUIDUtils(自定义类,非系统类)的get方法,这时候就需要用到JNIEnv,而每个线程都有独立的JNIEnv,可以通过JavaVM获取到每个线程关联的JNIEnv,那么问题就在于如何获取JavaVM?首先我们来了解一下JavaVM,JavaVM代表的是Java虚拟机,所有的工作都是从JavaVM开始的。获取JavaVM的方法有两种:

    1. 在JNI_Onload函数中获取
    2. 在主线程中调用(*env)->GetJavaVM(env, &javaVM)获取
    

    废话不多说,直接上代码:

    #include <jni.h>
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include "android/log.h"
    #define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO, "zp", FORMAT, ##__VA_ARGS__);
    #define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR, "zp", FORMAT, ##__VA_ARGS__);
    
    JavaVM *javaVM;
    //动态库被加载时,执行该函数
    //返回值JNI_VERSION_1_4,表示兼容Android SDK 2.2之后,2.2及2.2之前没有这个函数
    JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        LOGI("%s", "JNI_OnLoad");
        javaVM = vm;
        return JNI_VERSION_1_4;
    }
    
    void* thr_fun(void* arg) {
        JNIEnv* jniEnv = NULL;
        //第三个参数NULL表示什么
        //通过JavaVM关联当前线程,获取当前线程的JNIEnv
        (*javaVM)->AttachCurrentThread(javaVM, &jniEnv, NULL);
        if (jniEnv == NULL) {
            LOGI("%s", "jniEnv NULL");
        } else {
            LOGI("%s", "jniEnv not NULL")
        }
        jclass jcls = (*jniEnv)->FindClass(jniEnv, "com/zhangpan/ndksample/UUIDUtils");
        if (jcls == NULL) {
            LOGI("%s", "jcls NULL");
        } else {
            LOGI("%s", "jcls not NULL")
        }
        jmethodID jmid = (*jniEnv)->GetStaticMethodID(jniEnv, jcls, "get", "()Ljava/lang/String;");
        if (jmid == NULL) {
            LOGI("%s", "jmid NULL");
        } else {
            LOGI("%s", "jmid not NULL")
        }
    
        char* no = (char*)arg;
        int i = 0;
        for (; i < 5; i++) {
            LOGI("thread %s,i:%d", no, i);
            jobject uuid = (*jniEnv)->CallStaticObjectMethod(jniEnv, jcls, jmid);
            char* uuid_cstr = (*jniEnv)->GetStringUTFChars(jniEnv, uuid, NULL);
            LOGI("%s", uuid_cstr);
            if (i == 4) {
                pthread_exit((void*)0);
            }
            (*jniEnv)->ReleaseStringUTFChars(jniEnv, uuid, uuid_cstr);
            sleep(1);
        }
    
        //解除关联..
        (*javaVM)->DetachCurrentThread(javaVM);
    }
    
    //JavaVM 代表的是Java虚拟机,所有的工作都是从JavaVM开始的
    //可以通过JavaVM获取到每个线程关联的JNIEnv
    //每个线程都有独立的JNIEnv
    //如何获取JavaVM
    //1. 在JNI_OnLoad函数中获取
    //2. (*env)->GetJavaVM(env, &javaVM)
    JNIEXPORT void JNICALL
    Java_com_zhangpan_ndksample_PosixThread_pthread(JNIEnv *env, jobject instance) {
    //    (*env)->GetJavaVM(env, &javaVM);
        pthread_t tid;
        pthread_create(&tid, NULL, thr_fun, "NO1");
    }
    

    上述代码在子线程中调用findClass方法,返回的jclass为NULL,百思不得其解,这时候,我去找Java中的字符串类String,发现是可以找到的,后来我在想是不是由于自定义类在子线程中通过findClass找不到呢,最后将findClass方法放在主线程中去调用,发现返回的jclass不为NULL了,证实了我的猜想,但是具体原因需要去查看C的源码。

    接下来我将在主线程中调用findClass方法,这里我创建了一个初始化方法init,该方法在Android主线程中被调用,在init方法中去调用findClass方法找到UUIDUtils类。

    #include <jni.h>
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include "android/log.h"
    #define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO, "zp", FORMAT, ##__VA_ARGS__);
    #define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR, "zp", FORMAT, ##__VA_ARGS__);
    
    JavaVM *javaVM;
    jobject jcls_global;
    
    //动态库被加载时,执行该函数
    //返回值JNI_VERSION_1_4,表示兼容Android SDK 2.2之后,2.2及2.2之前没有这个函数
    JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        LOGI("%s", "JNI_OnLoad");
        javaVM = vm;
        return JNI_VERSION_1_4;
    }
    
    void* thr_fun(void* arg) {
        JNIEnv* jniEnv = NULL;
        //第三个参数NULL表示什么
        //通过JavaVM关联当前线程,获取当前线程的JNIEnv
        (*javaVM)->AttachCurrentThread(javaVM, &jniEnv, NULL);
        if (jniEnv == NULL) {
            LOGI("%s", "jniEnv NULL");
        } else {
            LOGI("%s", "jniEnv not NULL")
        }
        jmethodID jmid = (*jniEnv)->GetStaticMethodID(jniEnv, jcls_global, "get", "()Ljava/lang/String;");
        if (jmid == NULL) {
            LOGI("%s", "jmid NULL");
            return 0;
        } else {
            LOGI("%s", "jmid not NULL")
        }
    
        char* no = (char*)arg;
        int i = 0;
        for (; i < 5; i++) {
            LOGI("thread %s,i:%d", no, i);
            jobject uuid = (*jniEnv)->CallStaticObjectMethod(jniEnv, jcls_global, jmid);
            char* uuid_cstr = (*jniEnv)->GetStringUTFChars(jniEnv, uuid, NULL);
            LOGI("%s", uuid_cstr);
            if (i == 4) {
                goto end;
            }
            (*jniEnv)->ReleaseStringUTFChars(jniEnv, uuid, uuid_cstr);
            sleep(1);
        }
    end:
        //解除关联..
        (*javaVM)->DetachCurrentThread(javaVM);
        pthread_exit((void*)0);
    }
    
    //JavaVM 代表的是Java虚拟机,所有的工作都是从JavaVM开始的
    //可以通过JavaVM获取到每个线程关联的JNIEnv
    //每个线程都有独立的JNIEnv
    //如何获取JavaVM
    //1. 在JNI_OnLoad函数中获取
    //2. (*env)->GetJavaVM(env, &javaVM)
    JNIEXPORT void JNICALL
    Java_com_zhangpan_ndksample_PosixThread_pthread(JNIEnv *env, jobject instance) {
    //    (*env)->GetJavaVM(env, &javaVM);
        pthread_t tid;
        pthread_create(&tid, NULL, thr_fun, "NO1");
    }
    
    //初始化
    JNIEXPORT void JNICALL
    Java_com_zhangpan_ndksample_PosixThread_init(JNIEnv *env, jobject instance) {
        jclass jcls = (*env)->FindClass(env, "com/zhangpan/ndksample/UUIDUtils");
        jcls_global = (*env)->NewGlobalRef(env, jcls);
        if (jcls == NULL) {
            LOGI("%s", "jcls is NULL");
            return;
        } else {
            LOGI("%s", "jcls not NULL")
        }
    }
    
    //销毁
    JNIEXPORT void JNICALL
    Java_com_zhangpan_ndksample_PosixThread_destory(JNIEnv *env, jobject instance) {
        (*env)->DeleteGlobalRef(env, jcls_global);
    }
    

    pthread.java中:

    public class PosixThread {
        public native void init();
        public native void pthread();
        public native void destory();
    
        static {
            System.loadLibrary("zp_linux");
        }
    }
    

    UUIDUtils.java中:

    public class UUIDUtils {
        public static String get() {
            return UUID.randomUUID().toString();
        }
    }
    

    MainActivity.java中有一个按钮,给其设置点击事件,去调用pthread方法:

    public class MainActivity extends AppCompatActivity {
    
        private PosixThread posixThread;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            posixThread = new PosixThread();
            posixThread.init();
    
            TextView btnTest = (TextView) findViewById(R.id.btnTest);
            btnTest.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    posixThread.pthread();
                }
            });
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            posixThread.destory();
        }
    }
    

    运行结果为:

    1.png

    这样,我们就实现了在子线程中调用Java中的方法get(UUIDUtils中的方法)。

    喜欢本篇博客的简友们,就请来一波点赞,您的每一次关注,将成为我前进的动力,谢谢!

    相关文章

      网友评论

        本文标题:NDK多线程的使用与问题

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