核心要点

JNI 环境下,进行多线程编程,有以下两点是需清晰的:

  • JNIEnv 是一个线程效果域的变量,不能跨线程传递,每个线程都有自己的 JNIEnv 且互相独立
  • 部分引证不能在本地函数中跨函数运用,不能跨线前运用,当然也不能直接缓存起来运用

示例程序

示例程序首要演示:

  • 如何在子线程获取到属于子线程自己的 JNIEnv
  • 上面说了部分引证不能再线程之间直接传递,所以咱们只有另觅他法。

Java 层:

public void javaCallback(int count) {
    Log.e(TAG, "onNativeCallBack : " + count);
}
public native void threadTest();

Native 层:

static int count = 0;
JavaVM *gJavaVM = NULL;//大局 JavaVM 变量
jobject gJavaObj = NULL;//大局 Jobject 变量
jmethodID nativeCallback = NULL;//大局的办法ID
//这儿经过标志位来确定 两个线程的工作都完成了再履行 DeleteGlobalRef
//当然也能够经过加锁实现
bool main_finished = false;
bool background_finished = false;
static void *native_thread_exec(void *arg) {
    LOGE(TAG, "nativeThreadExec");
    LOGE(TAG, "The pthread id : %d\n", pthread_self());
    JNIEnv *env;
    //从大局的JavaVM中获取到环境变量
    gJavaVM->AttachCurrentThread(&env, NULL);
    //线程循环
    for (int i = 0; i < 5; i++) {
        usleep(2);
        //跨线程回调Java层函数
        env->CallVoidMethod(gJavaObj, nativeCallback, count++);
    }
    gJavaVM->DetachCurrentThread();
    background_finished = true;
    if (main_finished && background_finished) {
        env->DeleteGlobalRef(gJavaObj);
        LOGE(TAG, "大局引证在子线程毁掉");
    }
    return ((void *) 0);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_threadTest(JNIEnv *env, jobject thiz) {
    //创建大局引证,方便其他函数或线程运用
    gJavaObj = env->NewGlobalRef(thiz);
    jclass clazz = env->GetObjectClass(thiz);
    nativeCallback = env->GetMethodID(clazz, "javaCallback", "(I)V");
    //保存大局 JavaVM,留意 JavaVM 不是 JNI 引证类型
    env->GetJavaVM(&gJavaVM);
    pthread_t id;
    if (pthread_create(&id, NULL, native_thread_exec, NULL) != 0) {
        return;
    }
    for (int i = 0; i < 5; i++) {
        usleep(20);
        //跨线程回调Java层函数
        env->CallVoidMethod(gJavaObj, nativeCallback, count++);
    }
    main_finished = true;
    if (main_finished && background_finished && !env->IsSameObject(gJavaObj, NULL)) {
        env->DeleteGlobalRef(gJavaObj);
        LOGE(TAG, "大局引证在主线程毁掉");
    }
}

示例代码中,咱们的子线程需要运用主线程中的 jobject thiz,该变量是一个部分引证,不能赋值给一个大局变量然后跨线程跨函数运用,咱们经过 NewGlobalRef 将部分引证装换为大局引证并保存在大局变量 jobject gJavaObj 中,在运用完成后咱们需要运用 DeleteGlobalRef 来开释大局引证,因为多个线程履行次序的不确定性,咱们运用了标志位来保证两个线程所有的工作完成后再履行开释操作。

JNIEnv 是一个线程效果域的变量,不能跨线程传递,每个线程都有自己的 JNIEnv 且互相独立,实际开发中,咱们经过以下代码:

JavaVM *gJavaVM = NULL;
//主线程获取到 JavaVM
env->GetJavaVM(&gJavaVM);
//子线程经过 JavaVM 获取到自己的 JNIEnv
JNIEnv *env;
gJavaVM->AttachCurrentThread(&env, NULL);

在子线程中获取到 JNIEnv。JavaVM 是一个一般指针,由 JVM 来办理其内存的分配与回收,不是 JNI 引证类型,所以 咱们能够把它赋值给一个大局变量,直接用,也不用考虑他的内存分配与背工问题。

关于

我叫阿豪,2015 年本科结业于国防科技大学指挥自动化专业,结业后,从事信息化装备的研制工作。首要研究方向为 Android Framework 与 Linux Kernel,2023年春节后开始做 Android Framework 相关的技能分享。

假如你对 Framework 感兴趣或许正在学习 Framework,能够参阅我总结的Android Framework 学习道路攻略,也可关注我的微信大众号,我会在大众号上持续分享我的经历,协助正在学习的你少走一些弯路。学习过程中假如你有疑问或许你的经历想要分享给我们能够增加我的微信,我拉你进技能交流群。

JNI 编程上手指南之多线程