Android JNI学习10-AndroidStudio3.6 在JNI中创建JNI线程,并且实现在JNI线程中调用java层的方法

链接:https://pan.baidu.com/s/1zB9r5WwuTMIPNMlYl5HlvA 
提取码:5ndl

来模拟一个小功能,在JNI中,创建一个线程,用于下载文件,当文件下载成功之后,通知java层的方法进行更新。

开始实践吧

首先在MainActivity中创建native方法downloadThread

编写downloadThread对应的实现函数

extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnistudy_MainActivity_downloadThread(JNIEnv *env, jobject thiz) {
    
}

在JNI的downloadThread中,创建一个JNI线程

pthread_t pid;
    //启动线程 下载视频
    pthread_create(&pid,NULL,threadTask,thiz);

在此说明下pthread_create的几个参数

pid,是线程的句柄,当线程创建好后,会将具体的句柄值传递过pid中,所谓的句柄,其实就是对象的一个标识符,通过这个标识符就能找到其他所在的内存地址。

第二个参数是一个结构体,该结构体中,你可以去定义一些线程的具体属性,此处直接传递NULL即可.

第三个参数是一个函数指针,有点类型java中的run方法,到时候,创建好的线程会回调这个函数,现在还没有创建threadTask函数,那么我们就立即创建一个吧


void* threadTask(void* args){

    return 0;
}

第四个参数,其实就是第三个函数的参数,那么在此,我们可以直接将thiz传递过去,,thiz实际上就是MainActivity对象,到时我们想要调用MainActivity中的方法,就要用到这个参数。

那么好,现在线程我们已经创建好了,接下来就来完善threadTask中的逻辑

在JNI中想要获取java对象的Class,需要JNIEnv对象,在此有个误区,有的朋友可能会使用如下的JNIEnv

Android JNI学习10-AndroidStudio3.6 在JNI中创建JNI线程,并且实现在JNI线程中调用java层的方法_第1张图片

但我想告诉你,你使用上面的env对象绝对是错误的,因为这个env对象是在主线程中创建的,它不能被应用于子线程。那么问题来了,现在我在子线程中又非得要env对象,这个时候应该怎么办呢?答案就是利用java虚拟机,让它帮我们再创建一个适用于我们当前线程的env对象,做法如下。

在JNI_OnLoad中将JavaVM *vm 的vm作为全局变量,以便我们在子线程中使用

Android JNI学习10-AndroidStudio3.6 在JNI中创建JNI线程,并且实现在JNI线程中调用java层的方法_第2张图片

JavaVM它就代表的是java虚拟机,它能帮助我们创建一个env对象,创建env对象的代码如下

JNIEnv *env;
jint i = _vm->AttachCurrentThread(&env,0);
if (i != JNI_OK){
    return 0;
}

那么好,现在env对象有了,接下来就可以获取MainActivity的class了,代码如下

jclass cls = env->GetObjectClass(static_cast(args));

有了class还不够,还要被调用方法的MethodId,MethodId获取如下

jmethodID  updateUI = env->GetMethodID(cls,"updateUI","()V");

此时,MainActivity中,还没有updateUI这个方法,现在我们去创建一个

public void updateUI(){
        if (Looper.myLooper() == Looper.getMainLooper()){
            System.out.println("更新UI");
        }else{
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("更新UI");
                }
            });
        }
    }

class有了,MethodId也有了,那么就可以调用了

env->CallVoidMethod(args,updateUI);

最后当线程执行完毕后,记得要让当前线程和java虚拟机分离,不要让两者之间有过多的牵扯

//分离
_vm->DetachCurrentThread();

到此,线程部分算是写完了,接下来,就来运行测试下吧

在MainActivity中调用downloadThread

运行的时候,程序直接奔溃了,报了如下一个错误。

 JNI DETECTED ERROR IN APPLICATION: use of invalid jobject 

该错误说我们用了一个无效的jobject,其实问题就出在这

Android JNI学习10-AndroidStudio3.6 在JNI中创建JNI线程,并且实现在JNI线程中调用java层的方法_第3张图片

因此,我们主要thiz变为全局变量就行了,改变方式如下,利用NewGlobalRef

Android JNI学习10-AndroidStudio3.6 在JNI中创建JNI线程,并且实现在JNI线程中调用java层的方法_第4张图片

另外,在线程结束后,记得要释放thiz这个全局变量,代码如下

完整代码如下

void* threadTask(void* args){
    // native线程 附加 到 Java 虚拟机
    JNIEnv *env;
    jint i = _vm->AttachCurrentThread(&env,0);
    if (i != JNI_OK){
        return 0;
    }

    //假设,到此下载文件完成,该通知MainActivity了
    //获得MainActivity的class对象
    jclass cls = env->GetObjectClass(static_cast(args));
    // 反射获得方法
    jmethodID  updateUI = env->GetMethodID(cls,"updateUI","()V");
    env->CallVoidMethod(static_cast(args), updateUI);

    env->DeleteGlobalRef(static_cast(args));
    //分离
    _vm->DetachCurrentThread();

    return 0;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnistudy_MainActivity_downloadThread(JNIEnv *env, jobject thiz) {
    pthread_t pid;
    //启动线程 下载视频
    jobject globalThiz = env->NewGlobalRef(thiz);
    pthread_create(&pid,NULL,threadTask,globalThiz);
}

再次运行,效果如下

Android JNI学习10-AndroidStudio3.6 在JNI中创建JNI线程,并且实现在JNI线程中调用java层的方法_第5张图片

你可能感兴趣的:(Android,NDK)