首页 > 其他分享 >Android JNI 中的线程操作

Android JNI 中的线程操作

时间:2022-10-13 16:33:11浏览次数:77  
标签:Java void 线程 pthread Android NULL JNI


公众号回复:OpenGL,领取学习资源大礼包


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

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

我们可以在 Native 代码中使用 POSIX 线程,就相当于使用一个库一样,首先需要包含这个库的头文件:

1#include <pthread.h>

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

创建线程

POSIX 创建线程的函数如下:

1int pthread_create(
2 pthread_t* __pthread_ptr,
3 pthread_attr_t const* __attr,
4 void* (*__start_routine)(void*),
5 void* arg);

它的参数对应如下:

  • __pthread_ptr 为指向 pthread_t 类型变量的指针,用它代表返回线程的句柄。
  • __attr 为指向 pthread_attr_t 结构的指针,可以通过该结构来指定新线程的一些属性,比如栈大小、调度优先级等,具体看 pthread_attr_t 结构的内容。如果没有特殊要求,可使用默认值,把该变量取值为 NULL 。
  • 第三个参数为该线程启动程序的函数指针,也就是线程启动时要执行的那个方法,类似于 Java Runnable 中的 run 方法,它的函数签名格式如下:
1void* start_routine(void* args)
2

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

  • 第四个参数为线程启动程序的参数,也就是函数的参数,如果不需要传递参数,它可以为 NULL 。

​pthread_create​​ 函数如果执行成功了则返回 0 ,如果返回其他错误代码。

接下来,我们可以体验一下 pthread_create 方法创建线程。

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

1void *printThreadHello(void *);
2void *printThreadHello(void *) {
3 LOGD("hello thread");
4 // 切记要有返回值
5 return NULL;
6}

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

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

接下来创建线程:

 1JNIEXPORT void JNICALL
2Java_com_glumes_cppso_jnioperations_ThreadOps_createNativeThread(JNIEnv *, jobject) {
3 pthread_t handles; // 线程句柄
4 int result = pthread_create(&handles, NULL, printThreadHello, NULL);
5 if (result != 0) {
6 LOGD("create thread failed");
7 } else {
8 LOGD("create thread success");
9 }
10}

由于没有给该线程设置属性,并且线程运行函数也不需要参数,就都直接设置为了 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 加载时来获得,通过全局变量保存起来

1static JavaVM *gVm = NULL;
2JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
3 JNIEnv *env;
4 if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
5 return JNI_ERR;
6 }
7 gVm = vm;
8 return JNI_VERSION_1_6;
9}

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

具体使用如下:

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

1    private void printThreadName() {
2 LogUtil.Companion.d("print thread name current thread name is " + Thread.currentThread().getName());
3 Thread.sleep(5000);
4 }

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

 1void *run(void *);
2void *run(void *args) {
3 JNIEnv *env = NULL;
4 // 将当前线程添加到 Java 虚拟机上
5 // 假设有参数传递
6 ThreadRunArgs *threadRunArgs = (ThreadRunArgs *) args;
7 if (gVm->AttachCurrentThread(&env, NULL) == 0) {
8 // 回调 Java 的方法,打印当前线程 id ,发现不是主线程就对了
9 env->CallVoidMethod(gObj, printThreadName);
10 // 从 Java 虚拟机上分离当前线程
11 gVm->DetachCurrentThread();
12 }
13 return (void *) threadRunArgs->result;
14}

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

1        // 创建传递的参数
2 ThreadRunArgs *threadRunArgs = new ThreadRunArgs();
3 threadRunArgs->id = i;
4 threadRunArgs->result = i * i;
5 // 运行线程
6 int result = pthread_create(&handles, NULL, run, (void *) threadRunArgs);

通过这样的调用,就可以在 Native 线程中调用 Java 相关的函数了。

等待线程返回结果

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

现在,还可以在该方法里等待线程执行完毕后,拿到线程执行完的结果之后再推出。

通过 ​​pthread_join​​ 方法可以等待线程终止。

1int pthread_join(pthread_t __pthread, void** __return_value_ptr);

其中:

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

使用如下:

 1    for (int i = 0; i < num; ++i) {
2 pthread_t pthread;
3 // 创建线程,
4 int result = pthread_create(&handles[i], NULL, run, (void *) threadRunArgs);
5 }
6 }
7 for (int i = 0; i < num; ++i) {
8 void *result = NULL; // 线程执行返回结果
9 // 等待线程执行结束
10 if (pthread_join(handles[i], &result) != 0) {
11 throwByName(env, runtimeException, "Unable to join thread");
12 } else {
13 LOGD("return value is %d",result);
14 }
15 }

如果 pthread_join 返回为 0 代表执行成功,非 0 则执行失败。

具体的代码示例可以参考我的 Github 项目,欢迎 Star 。

​https://github.com/glumes/AndroidDevWithCpp​

JNI 学习系列文章:

  1. ​Android JNI 中的引用管理​
  2. ​Android JNI 调用时的异常处理​
  3. ​Android JNI 基础知识​
  4. ​Android  JNI 调用时缓存字段和方法 ID​

Android JNI 中的线程操作_java



扫码关注公众号【音视频开发进阶】,一起学习多媒体音视频开发~~~

Android JNI 中的线程操作_java_02


喜欢就点个吧 ▽


扫码关注


标签:Java,void,线程,pthread,Android,NULL,JNI
From: https://blog.51cto.com/u_12127193/5753721

相关文章

  • HarmonyOS 到底是不是 Android 套皮?
    6月2号,华为自研的操作系统 HarmonyOS 2.0的beta版本正式发布,在业内引起了极大的讨论。一方面,这是中国首个自主知识产权的操作系统,现在已经开源,且正式面向市场开始......
  • Java并发(线程状态、线程调度、线程同步)
    Java并发(线程状态、线程调度、线程同步)线程状态​ 线程共有5种状态,在特定情况下,线程可以在不同的状态之间切换。5种具体状态创建状态:实例化一个新的线程对象,还未启......
  • Java并发(进程、线程、多线程,使用)
    Java并发(进程、线程、多线程,使用)进程和线程定义进程:进程是计算机正在运行的一个独立的应用程序。线程:线程是组成进程的基本单位,可以完成特定的功能,一个进程是由一个......
  • 通过一个对象下的多线程模拟龟兔赛跑
    packagedemo1;importjava.util.Objects;publicclassRaceimplementsRunnable{privatestaticStringwinner;//定义一个胜利者@Overridepublicv......
  • 使用gdb调试多进程和多线程程序
    默认设置下,在调试多进程程序时GDB只会调试主进程。但是GDB(>V7.0)支持多进程的分别以及同时调试,换句话说,GDB可以同时调试多个程序。只需要设置follow-fork-mode(默认值:parent)......
  • Android技术分享| Bugly 应用升级自定义UI
    最近项目里的采用免费的Bugly应用升级功能,由于默认的UI非常的简陋且与项目整体风格不搭,所以需要自定义UI,本篇文章记录在实现过程中的一些注意事项。根据官方文档可知,自定......
  • 多线程下载网图
    packagedemo1;importorg.apache.commons.io.FileUtils;importjava.io.File;importjava.io.IOException;importjava.net.URL;publicclassTestThread2extends......
  • 线程的一些概念
    cpu的核心同一时间只能执行一段指令,线程是进程的最小执行流,每个线程指向一个方法体,当方法执行完毕后,线程释放前台线程与后台线程:一个进程退出的标志是:所有的前台线程结......
  • 线程池 keepAlivetime 参数详解
    线程池七大参数 核心线程数,最大线程数,任务队列,超时时间,时间单位,线程工厂,拒绝策略线程池执行流程:添加一个任务到线程池 -> 判断当前线程数量是否大于核心线程数 ......
  • Android Studio 出现 Failed to create JVM
    failedtocreateJVM:JVMpath:"C:\ProgramFiles\XXXXXX\XXXX\xxxxx\"ifyouhavea64-bitjdkinstalled,defineaJAVA_HOMEvariableinComputer>systemv......