Android JNI 中的线程操做

学习一下如何在 Native 代码中使用线程。java

Native 中支持的线程标准是 POSIX 线程,它定义了一套建立和操做线程的 API 。android

咱们能够在 Native 代码中使用 POSIX 线程,就至关于使用一个库同样,首先须要包含这个库的头文件:git

#include <pthread.h>
复制代码

这个头文件中定义了不少和线程相关的函数,这里就暂时使用到了其中部份内容。github

建立线程

POSIX 建立线程的函数以下:微信

int pthread_create(
	pthread_t* __pthread_ptr, 
	pthread_attr_t const* __attr, 
	void* (*__start_routine)(void*), 
	void* arg);
复制代码

它的参数对应以下:函数

  • __pthread_ptr 为指向 pthread_t 类型变量的指针,用它表明返回线程的句柄。post

  • __attr 为指向 pthread_attr_t 结构的指针,能够经过该结构来指定新线程的一些属性,好比栈大小、调度优先级等,具体看 pthread_attr_t 结构的内容。若是没有特殊要求,可以使用默认值,把该变量取值为 NULL 。学习

  • 第三个参数为该线程启动程序的函数指针,也就是线程启动时要执行的那个方法,相似于 Java Runnable 中的 run 方法,它的函数签名格式以下:spa

void* start_routine(void* args) 复制代码

启动程序将线程参数当作 void 指针,返回 void 指针类型结果。线程

  • 第四个参数为线程启动程序的参数,也就是函数的参数,若是不须要传递参数,它能够为 NULL 。

pthread_create 函数若是执行成功了则返回 0 ,若是返回其余错误代码。

接下来,咱们能够体验一下 pthread_create 方法建立线程。

首先,建立线程启动时运行的函数:

void *printThreadHello(void *);
void *printThreadHello(void *) {
    LOGD("hello thread");
    // 切记要有返回值
    return NULL;
}
复制代码

要注意线程启动函数是要有返回值的,没有返回值就直接崩溃了。

另外这个函数一旦 pthread_create 调用了,它就会当即执行。

接下来建立线程:

JNIEXPORT void JNICALL Java_com_glumes_cppso_jnioperations_ThreadOps_createNativeThread(JNIEnv *, jobject) {
    pthread_t handles; // 线程句柄
    int result = pthread_create(&handles, NULL, printThreadHello, NULL);
    if (result != 0) {
        LOGD("create thread failed");
    } else {
        LOGD("create thread success");
    }
}
复制代码

因为没有给该线程设置属性,而且线程运行函数也不须要参数,就都直接设置为了 NULL,那么上面那段程序就能够执行了,而且 printThreadHello 函数是运行在新的线程中的。

将线程附着在 Java 虚拟机上

在上面的线程启动函数中,只是简单的执行了打印 log 的操做,若是想要执行和 Java 相关的操做,好比从 JNI 调用 Java 的函数等等,那就须要用到 Java 虚拟机环境了,也就是用到 JNIEnv 指针,毕竟全部的调用函数都是以它开头的。

pthread_create 建立的线程是一个 C++ 中的线程,虚拟机并不能识别它们,为了和 Java 空间交互,须要先把 POSIX 线程附着到 Java 虚拟机上,而后就能够得到当前线程的 JNIEnv 指针,由于 JNIEnv 指针只是在当前线程中有效。

经过 AttachCurrentThread 方法能够将当前线程附着到 Java 虚拟机上,而且能够得到 JNIEnv 指针。

AttachCurrentThread 方法是由 JavaVM 指针调用的,它表明的是 Java 虚拟机接口指针,能够在 JNI_OnLoad 加载时来得到,经过全局变量保存起来

static JavaVM *gVm = NULL;
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    gVm = vm;
    return JNI_VERSION_1_6;
}
复制代码

当经过 AttachCurrentThread 方法将线程附着当 Java 虚拟机上后,还须要将该线程从 Java 虚拟机上分离,经过 DetachCurrentThread 方法,这两个方法是要同时使用的,不然会带来 BUG 。

具体使用以下:

首先在 Java 中定义在 C++ 线程中回调的方法,主要就是打印线程名字:

private void printThreadName() {
        LogUtil.Companion.d("print thread name current thread name is " + Thread.currentThread().getName());
            Thread.sleep(5000);
    }
复制代码

而后定义 Native 线程中运行的方法:

void *run(void *);
void *run(void *args) {
    JNIEnv *env = NULL;
    // 将当前线程添加到 Java 虚拟机上
    // 假设有参数传递
    ThreadRunArgs *threadRunArgs = (ThreadRunArgs *) args;
    if (gVm->AttachCurrentThread(&env, NULL) == 0) {
    // 回调 Java 的方法,打印当前线程 id ,发现不是主线程就对了
        env->CallVoidMethod(gObj, printThreadName);
        // 从 Java 虚拟机上分离当前线程
        gVm->DetachCurrentThread();  
    }
    return (void *) threadRunArgs->result;
}
复制代码

最后建立线程并运行方法:

// 建立传递的参数
	    ThreadRunArgs *threadRunArgs = new ThreadRunArgs();
        threadRunArgs->id = i;
        threadRunArgs->result = i * i;
        // 运行线程
        int result = pthread_create(&handles, NULL, run, (void *) threadRunArgs);
复制代码

经过这样的调用,就能够在 Native 线程中调用 Java 相关的函数了。

等待线程返回结果

前面提到在线程运行函数中必需要有返回值,最开始只是返回了一个空指针 NULL ,而且在某个方法里面开启了新线程,新线程运行后,该方法也就当即返回退出,执行完了。

如今,还能够在该方法里等待线程执行完毕后,拿到线程执行完的结果以后再推出。

经过 pthread_join 方法能够等待线程终止。

int pthread_join(pthread_t __pthread, void** __return_value_ptr);
复制代码

其中:

  • __pthread 表明建立线程的句柄
  • __return_value_ptr 表明线程运行函数返回的结果

使用以下:

for (int i = 0; i < num; ++i) {
        pthread_t pthread;
        // 建立线程,
        int result = pthread_create(&handles[i], NULL, run, (void *) threadRunArgs);
        }
    }
    for (int i = 0; i < num; ++i) {
        void *result = NULL; // 线程执行返回结果
        // 等待线程执行结束
        if (pthread_join(handles[i], &result) != 0) {
            throwByName(env, runtimeException, "Unable to join thread");
        } else {
	        LOGD("return value is %d",result);
        }
    }
复制代码

若是 pthread_join 返回为 0 表明执行成功,非 0 则执行失败。

具体的代码示例能够参考个人 Github 项目,欢迎 Star 。

github.com/glumes/Andr…

JNI 系列文章:

  1. Android JNI 中的引用管理
  2. Android JNI 调用时的异常处理
  3. Android JNI 基本操做

欢迎关注微信公众号:【纸上浅谈】,得到最新文章推送~~~

扫码关注
相关文章
相关标签/搜索