文章目录
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]