当前位置: 首页 > news >正文

Android 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


技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

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

喜欢就点个「在看」吧 ▽

扫码关注

相关文章:

  • 【音视频连载-004】基础学习篇-SDL 加载图片并显示
  • OpenGL 利用 Alpha 透明度进行测试
  • 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示
  • 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件
  • Android 使用 OpenGL ES 绘制球面
  • 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上)
  • PBO是OpenGL最高效的像素拷贝方式吗?
  • 游戏开发入门(一)游戏发展史
  • 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下)
  • memcpy速度太慢?掌握这个技术让内存拷贝效率成倍提升
  • DXOMark是如何评价音频质量的
  • 【每周一记-001】~~~
  • 【音视频连载-009】第二季 FFmpeg 打造简易播放器
  • 【每周一记-002】
  • iOS中使用OpenGL 实现增高功能
  • [NodeJS] 关于Buffer
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • emacs初体验
  • ES6 学习笔记(一)let,const和解构赋值
  • extract-text-webpack-plugin用法
  • FineReport中如何实现自动滚屏效果
  • JavaScript DOM 10 - 滚动
  • JavaScript设计模式与开发实践系列之策略模式
  • markdown编辑器简评
  • MySQL的数据类型
  • Spark RDD学习: aggregate函数
  • 程序员最讨厌的9句话,你可有补充?
  • 基于游标的分页接口实现
  • 问题之ssh中Host key verification failed的解决
  • ​一些不规范的GTID使用场景
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • (1)虚拟机的安装与使用,linux系统安装
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (2)STL算法之元素计数
  • (4)logging(日志模块)
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (Python) SOAP Web Service (HTTP POST)
  • (二)windows配置JDK环境
  • (三)elasticsearch 源码之启动流程分析
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (五)IO流之ByteArrayInput/OutputStream
  • (五)网络优化与超参数选择--九五小庞
  • (转)详解PHP处理密码的几种方式
  • (转)用.Net的File控件上传文件的解决方案
  • .NET : 在VS2008中计算代码度量值
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • /bin/bash^M: bad interpreter: No such file or directory
  • @Mapper作用
  • [acm算法学习] 后缀数组SA
  • [Asp.net mvc]国际化
  • [BZOJ 3282] Tree 【LCT】
  • [C#]扩展方法
  • [EFI]Acer Aspire A515-54g电脑 Hackintosh 黑苹果efi引导文件