Android C++系列:JNI中的线程操作

简介: 第四个参数为线程启动程序的参数,也就是函数的参数,如果不需要传递参数,它可以为 NULL 。 pthread_create 函数如果执行成功了则返回 0 ,如果返回其他错误代码。

image.png


Java中创建线程三种方式:


  1. 继承Thread类创建线程类;
  2. 通过Runnable接口创建线程类;
  3. 通过Callable和Future创建线程。


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


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


#include <pthread.h>              


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


创建线程


POSIX 创建线程的函数如下:


int pthread_create(
   pthread_t* __pthread_ptr, 
   pthread_attr_t const* __attr, 
   void* (*__start_routine)(void*), 
   void* arg);


它的参数对应如下:


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


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 调用了,printThreadHello就会立即执行。


接下来我们使用JNI封装创建线程:


JNIEXPORT void JNICALL
 Java_com_qingkouwei_Demo_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 方法,这两个方法是要同时使用的,否则会出现问题 。


具体使用如下:


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


private void printThreadName() {
    Log.d(”DEMO","print thread name current thread name is " + Thread.currentThread().getName());
         Thread.sleep(5000);
 }


2.然后定义 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;
 }


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


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


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


等待线程返回结果


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


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


和Java的join方法类似,C中可以通过 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 则执行失败。


总结


本文介绍了通过C语言创建线程的方法和C层等待线程返回的方法,以及JNI提供的AttachCurrentThread 和 DetachCurrentThread方法,通过该方法将C线程附着到Java虚拟机,同时我们还介绍了JNI_OnLoad方法和JavaVM结构。

目录
相关文章
|
22天前
|
Java Android开发 C++
Android Studio JNI 使用模板:c/cpp源文件的集成编译,快速上手
本文提供了一个Android Studio中JNI使用的模板,包括创建C/C++源文件、编辑CMakeLists.txt、编写JNI接口代码、配置build.gradle以及编译生成.so库的详细步骤,以帮助开发者快速上手Android平台的JNI开发和编译过程。
68 1
|
7天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
25 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
9天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
29 10
|
7天前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
9天前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
1月前
|
Java 调度
基于C++11的线程池
基于C++11的线程池
|
1月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
37 4
|
15天前
|
JavaScript 安全 前端开发
ArkTS线程中通过napi创建的C++线程
需要注意的是,N-API和ArkTS的具体使用会随Node.js的版本不断更新和变化,所以在实际编写代码前,查看最新的官方文档是很重要的,以了解最新的最佳实践和使用模式。此外,C++线程的使用在Node.js插件中应当慎重,过多地使用它们可能会造成资源争用,并可能降低应用程序的性能。
30 0
|
21天前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享安卓与iOS开发中的线程管理比较
【8月更文挑战第30天】本文将探讨网络安全与信息安全的重要性,并分享关于网络安全漏洞、加密技术和安全意识的知识。我们将了解常见的网络攻击类型和防御策略,以及如何通过加密技术和提高安全意识来保护个人和组织的信息安全。
|
29天前
|
Java 测试技术 Android开发
Android项目架构设计问题之构造一个Android中的线程池如何解决
Android项目架构设计问题之构造一个Android中的线程池如何解决
24 0