【Android NDK 开发】JNI 线程 ( JNI 线程创建 | 线程执行函数 | 非 JNI 方法获取 JNIEnv 与 Java 对象 | 线程获取 JNIEnv | 全局变量设置 )

简介: 【Android NDK 开发】JNI 线程 ( JNI 线程创建 | 线程执行函数 | 非 JNI 方法获取 JNIEnv 与 Java 对象 | 线程获取 JNIEnv | 全局变量设置 )

文章目录

I . JNI 线程创建

II . 线程执行函数

III . 线程方法获取 Java 对象

IV . 线程方法获取 JNIEnv

V . JNI 线程 完整代码示例



I . JNI 线程创建


1. 线程创建方法函数原型 :


int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, (void*)(*start_rtn)(void*), void *arg)`;



2. pthread_create 方法的 4 个参数 ;


参数 1 ( pthread_t *tidp ) : 线程标识符指针 , 该指针指向线程标识符 ;

参数 2 ( const pthread_attr_t *attr ) : 线程属性指针 ;

参数 3 ( (void*)(*start_rtn)(void*) ) : 线程运行函数指针 , start_rtn 是一个函数指针 , 其参数和返回值类型是 void* 类型 ;

参数 4 ( void *arg ) : 参数 3 中的线程运行函数的参数 ;


3. 返回值说明 :


线程创建成功 , 返回 0 ;

线程创建失败 , 返回 错误代码 ;


4. 关于函数指针参数的说明 : C++ 中函数指针类型是 void *(PTW32_CDECL *start) (void *)


函数的参数类型是 void* 指针 ;

函数的返回值类型 void* 指针 ;


5. 函数多参数方案 : 如果线程执行的函数有多个参数 , 可以使用结构体 , 类进行封装 ;



6. 线程属性 : 创建线程时 , 给线程指定属性 pthread_attr_t 是结构体类型 ;



7. 代码示例 :


/*
  线程创建方法函数原型 : 
  int pthread_create(
    pthread_t *tidp, 
    const pthread_attr_t *attr, 
    (void*)(*start_rtn)(void*), 
    void *arg);
  该方法需要提供四个参数 ;
    参数 1 ( pthread_t *tidp ) :线程标识符指针 , 该指针指向线程标识符 ;
    参数 2 ( const pthread_attr_t *attr ) : 线程属性指针 ;
    参数 3 ( (void*)(*start_rtn)(void*) ) : 线程运行函数指针 , start_rtn 是一个函数指针 , 
    其参数和返回值类型是 void* 类型
    参数 4 ( void *arg ) : 参数 3 中的线程运行函数的参数 ;
  返回值 :
    线程创建成功 , 返回 0 ;
    线程创建失败 , 返回 错误代码 ;
  关于函数指针参数 : C++ 中函数指针类型是 void *(PTW32_CDECL *start) (void *) ,
    函数的参数类型是 void* 指针
    函数的返回值类型 void* 指针
  函数多参数方案 : 如果线程执行的函数有多个参数 , 可以使用结构体 , 类进行封装
  线程属性 : 创建线程时 , 给线程指定属性 pthread_attr_t 是结构体类型
  */
  //函数指针 函数名 和 &函数名 都可以作为函数指针
  pthread_create( &pid , 0 , threadRun, 0 );




II . 线程执行函数


1. 线程执行函数的要求 : C++ 中规定线程执行函数的函数指针类型是 void *(PTW32_CDECL *start) (void *) ;



2. 函数作用 : 将该函数的指针作为线程创建方法 pthread_create 的第三个参数 ;



3. 参数处理 : 在线程创建时 , 传入参数 , 将该参数转为 char* 字符串指针类型 , 将其打印出来 ;



4. 代码示例 :


/*
  定义线程中要执行的方法
  将该函数的指针作为线程创建方法 pthread_create 的第三个参数
  C++ 中规定线程执行函数的函数指针类型是 void *(PTW32_CDECL *start) (void *)
*/
void* pthread_function(void* args) {
  //延迟 100 ms 执行
  //_sleep(100);
  //指针类型转换 : 将 void* 转为 char*
  //  使用 static_cast 类型转换标识符
  char* hello = static_cast<char*>(args);
  //打印参数
  cout << "pthread_function 线程方法 执行 参数 : " << hello << endl;
  return 0;
}




III . 线程方法获取 Java 对象


线程方法获取 Java 对象步骤 :



① 定义全局变量 jobject obj : 使用该全局变量存储 Java 对象 ;


//JNI 方法参数中的第二个参数 , 需要先将局部变量转为全局变量 , 然后再其它方法中调用
jobject obj;



② JNI 方法处理 : 将 jobject instance 参数 ( 此时是局部变量 ) 转为 全局变量 , 调用 NewGlobalRef 方法实现 ;


void threadDemoC(JNIEnv *env, jobject instance){
    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadDemoC");
    //保存全局变量 , 先将局部变量转为全局变量 , 然后再赋值给全局的 obj 变量
    //  使用域作用符访问全局的 ::obj 变量
    ::obj = env->NewGlobalRef(instance);
    ...
}



这样就可以在其它方法或其它线程中使用该 Java 对象了 ;




IV . 线程方法获取 JNIEnv


线程中获取 JNIEnv * env 步骤 :



① JNIEnv 无法跨线程 : JNI 方法参数中的 JNIEnv 指针是不能跨线程使用的 , 在 主线程中调用 JNI 方法 , 其 JNIEnv 指针不能在子线程中使用 ;



② 获取途径 : 如果在子线程中使用 JNIEnv 指针 , 需要使用 JavaVM 获取 指定线程的 JNIEnv 指针 ;



③ 绑定线程 : 调用 JavaVM 的 AttachCurrentThread 方法 , 可以绑定线程 , 其传入一个 JNIEnv ** 二维指针 , 会返回该线程对应的 JNIEnv 指针 ;



④ 剥离线程 : 注意使用完 JNIEnv 后 , 解绑线程 , 调用 JavaVM 的 DetachCurrentThread 方法 解绑线程 ;



2 . 代码示例 :


/*
    线程执行的方法
    如果在 Native 层执行耗时操作 , 如下载文件 , 需要在线程中处理
    JNI 方法参数中的 JNIEnv 指针是不能跨线程使用的 , 在 主线程中调用 JNI 方法 , 其 JNIEnv 指针不能在子线程中使用
    如果在子线程中使用 JNIEnv 指针 , 需要使用 JavaVM 获取 指定线程的 JNIEnv 指针
        调用 JavaVM 的 AttachCurrentThread 可以获取本线程的 JNIEnv 指针
        注意最后还要将线程从 Java 虚拟机中剥离
    关于参数传递 :
        传递 int 类型  和 int * 类型 , 传递指针可以在 方法中修改 int 变量值 ;
        传递 int * 类型 和 int ** 类型 , 传递二维指针 可以在方法中修改 int * 一维指针值
        因此有些参数需要在方法中修改, 并且需要保存该修改状态 , 就需要将该变量的地址当做参数传入
            原来的普通变量 变成 指针变量 , 一维指针 变 二维指针
*/
void* threadRun(void *args){
    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadRun");
    //JNIEnv 不能跨线程使用 , 这里需要先获取本线程的 JNIEnv
    JNIEnv *env;
    //将线程附加到 Java 虚拟机中 ( 注意后面对应剥离线程操作 )
    //  如果成功返回 0 , 如果失败 , 直接退出
    int attachResult = _vm->AttachCurrentThread(&env, 0);
    ...
    //将线程从 Java 虚拟机中剥离
    _vm->DetachCurrentThread();
    //注意这里一定要返回 0 , 否则执行到结尾会崩溃
    return 0;
}



V . JNI 线程 完整代码示例


1 . Java 层代码 :


package kim.hsl.thread;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        threadDemoJava();
    }
    /**
     * JNI 线程 Demo
     */
    public native void threadDemoJava();
    /**
     * 打印当前线程信息
     */
    public void logThread(){
        Log.i("JNI_TAG", Thread.currentThread().toString());
    }
}



2 . Native 层代码 :


#include <jni.h>
#include <string>
#include <android/log.h>
//导入线程头文件
#include <pthread.h>
//Java 虚拟机指针 , 在 JNI_OnLoad 方法中设置该值
JavaVM *_vm;
//JNI 方法参数中的第二个参数 , 需要先将局部变量转为全局变量 , 然后再其它方法中调用
jobject obj;
/*
    线程执行的方法
    如果在 Native 层执行耗时操作 , 如下载文件 , 需要在线程中处理
    JNI 方法参数中的 JNIEnv 指针是不能跨线程使用的 , 在 主线程中调用 JNI 方法 , 其 JNIEnv 指针不能在子线程中使用
    如果在子线程中使用 JNIEnv 指针 , 需要使用 JavaVM 获取 指定线程的 JNIEnv 指针
        调用 JavaVM 的 AttachCurrentThread 可以获取本线程的 JNIEnv 指针
        注意最后还要将线程从 Java 虚拟机中剥离
    关于参数传递 :
        传递 int 类型  和 int * 类型 , 传递指针可以在 方法中修改 int 变量值 ;
        传递 int * 类型 和 int ** 类型 , 传递二维指针 可以在方法中修改 int * 一维指针值
        因此有些参数需要在方法中修改, 并且需要保存该修改状态 , 就需要将该变量的地址当做参数传入
            原来的普通变量 变成 指针变量 , 一维指针 变 二维指针
*/
void* threadRun(void *args){
    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadRun");
    //JNIEnv 不能跨线程使用 , 这里需要先获取本线程的 JNIEnv
    JNIEnv *env;
    //将线程附加到 Java 虚拟机中 ( 注意后面对应剥离线程操作 )
    //  如果成功返回 0 , 如果失败 , 直接退出
    int attachResult = _vm->AttachCurrentThread(&env, 0);
    //获取 MainActivity 对应的 jclass 对象
    jclass clazz = env->GetObjectClass( obj );
    //反射获取 logThread 方法
    jmethodID logThreadID = env->GetMethodID(clazz, "logThread", "()V");
    //调用 logThread 方法
    env->CallVoidMethod(obj, logThreadID);
    //释放相关的局部变量
    env->DeleteLocalRef(clazz);
    //将线程从 Java 虚拟机中剥离
    _vm->DetachCurrentThread();
    //注意这里一定要返回 0 , 否则执行到结尾会崩溃
    return 0;
}
void threadDemoC(JNIEnv *env, jobject instance){
    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadDemoC");
    //保存全局变量 , 先将局部变量转为全局变量 , 然后再赋值给全局的 obj 变量
    //  使用域作用符访问全局的 ::obj 变量
    ::obj = env->NewGlobalRef(instance);
    //代表一个线程的句柄
    pthread_t pid;
    //创建线程并执行
    pthread_create( &pid , 0 , threadRun, 0 );
}
//下面的代码是动态注册内容
static const JNINativeMethod methods[] = {
        {"threadDemoJava", "()V", (void *)threadDemoC}
};
static const char* className = "kim/hsl/thread/MainActivity";
int JNI_OnLoad(JavaVM *vm , void* reserved){
    // 1 . 记录 Java 虚拟机指针
    _vm = vm;
    // 2 . 动态注册方法
    //获取 JNIEnv 指针
    JNIEnv *env = nullptr;
    int registerResult = vm->GetEnv( (void **) &env, JNI_VERSION_1_6 );
    if(registerResult != JNI_OK){
        return -1;
    }
    //进行动态注册
    jclass jclazz = env->FindClass(className);
    env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));
    return JNI_VERSION_1_6;
}


3 . 执行结果 :


2020-02-08 23:47:58.253 25293-25293/? I/JNI_TAG: threadDemoC
2020-02-08 23:47:58.253 25293-25316/? I/JNI_TAG: threadRun
2020-02-08 23:47:58.253 25293-25316/? I/JNI_TAG: Thread[Thread-2,10,main]


目录
相关文章
|
4天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
26天前
|
Java Android开发
Android 开发获取通知栏权限时会出现两个应用图标
Android 开发获取通知栏权限时会出现两个应用图标
12 0
|
3天前
|
Android开发
Android JNI 报错(signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr )
Android JNI 报错(signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr )
20 1
|
23小时前
|
数据库 Android开发 开发者
安卓应用开发:构建高效用户界面的策略
【4月更文挑战第24天】 在竞争激烈的移动应用市场中,一个流畅且响应迅速的用户界面(UI)是吸引和保留用户的关键。针对安卓平台,开发者面临着多样化的设备和系统版本,这增加了构建高效UI的复杂性。本文将深入分析安卓平台上构建高效用户界面的最佳实践,包括布局优化、资源管理和绘制性能的考量,旨在为开发者提供实用的技术指南,帮助他们创建更流畅的用户体验。
|
2天前
|
Android开发
Android构建系统:Android.mk(2)函数详解
Android构建系统:Android.mk(2)函数详解
11 1
|
2天前
|
Android开发
Android JNI与CAN通信遇到的问题总结
Android JNI与CAN通信遇到的问题总结
21 1
|
2天前
|
存储 Java Android开发
Android系统 设置第三方应用为默认Launcher实现和原理分析
Android系统 设置第三方应用为默认Launcher实现和原理分析
16 0
|
11天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
17天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
20天前
|
监控 算法 Android开发
安卓应用开发:打造高效启动流程
【4月更文挑战第5天】 在移动应用的世界中,用户的第一印象至关重要。特别是对于安卓应用而言,启动时间是用户体验的关键指标之一。本文将深入探讨如何优化安卓应用的启动流程,从而减少启动时间,提升用户满意度。我们将从分析应用启动流程的各个阶段入手,提出一系列实用的技术策略,包括代码层面的优化、资源加载的管理以及异步初始化等,帮助开发者构建快速响应的安卓应用。