【Android NDK 开发】JNI 线程 ( JNI 线程创建 | 线程执行函数 | 非 JNI 方法获取 JNIEnv 与 Java 对象 | 线程获取 JNIEnv | 全局变量设置 )
文章目錄
- I . JNI 線程創(chuàng)建
- II . 線程執(zhí)行函數(shù)
- III . 線程方法獲取 Java 對(duì)象
- IV . 線程方法獲取 JNIEnv
- V . JNI 線程 完整代碼示例
I . JNI 線程創(chuàng)建
1. 線程創(chuàng)建方法函數(shù)原型 :
int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, (void*)(*start_rtn)(void*), void *arg)`;2. pthread_create 方法的 4 個(gè)參數(shù) ;
- 參數(shù) 1 ( pthread_t *tidp ) : 線程標(biāo)識(shí)符指針 , 該指針指向線程標(biāo)識(shí)符 ;
- 參數(shù) 2 ( const pthread_attr_t *attr ) : 線程屬性指針 ;
- 參數(shù) 3 ( (void*)(*start_rtn)(void*) ) : 線程運(yùn)行函數(shù)指針 , start_rtn 是一個(gè)函數(shù)指針 , 其參數(shù)和返回值類型是 void* 類型 ;
- 參數(shù) 4 ( void *arg ) : 參數(shù) 3 中的線程運(yùn)行函數(shù)的參數(shù) ;
3. 返回值說明 :
- 線程創(chuàng)建成功 , 返回 0 ;
- 線程創(chuàng)建失敗 , 返回 錯(cuò)誤代碼 ;
4. 關(guān)于函數(shù)指針參數(shù)的說明 : C++ 中函數(shù)指針類型是 void *(PTW32_CDECL *start) (void *)
- 函數(shù)的參數(shù)類型是 void* 指針 ;
- 函數(shù)的返回值類型 void* 指針 ;
5. 函數(shù)多參數(shù)方案 : 如果線程執(zhí)行的函數(shù)有多個(gè)參數(shù) , 可以使用結(jié)構(gòu)體 , 類進(jìn)行封裝 ;
6. 線程屬性 : 創(chuàng)建線程時(shí) , 給線程指定屬性 pthread_attr_t 是結(jié)構(gòu)體類型 ;
7. 代碼示例 :
/*線程創(chuàng)建方法函數(shù)原型 : int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, (void*)(*start_rtn)(void*), void *arg);該方法需要提供四個(gè)參數(shù) ;參數(shù) 1 ( pthread_t *tidp ) :線程標(biāo)識(shí)符指針 , 該指針指向線程標(biāo)識(shí)符 ;參數(shù) 2 ( const pthread_attr_t *attr ) : 線程屬性指針 ;參數(shù) 3 ( (void*)(*start_rtn)(void*) ) : 線程運(yùn)行函數(shù)指針 , start_rtn 是一個(gè)函數(shù)指針 , 其參數(shù)和返回值類型是 void* 類型參數(shù) 4 ( void *arg ) : 參數(shù) 3 中的線程運(yùn)行函數(shù)的參數(shù) ;返回值 :線程創(chuàng)建成功 , 返回 0 ;線程創(chuàng)建失敗 , 返回 錯(cuò)誤代碼 ;關(guān)于函數(shù)指針參數(shù) : C++ 中函數(shù)指針類型是 void *(PTW32_CDECL *start) (void *) ,函數(shù)的參數(shù)類型是 void* 指針函數(shù)的返回值類型 void* 指針函數(shù)多參數(shù)方案 : 如果線程執(zhí)行的函數(shù)有多個(gè)參數(shù) , 可以使用結(jié)構(gòu)體 , 類進(jìn)行封裝線程屬性 : 創(chuàng)建線程時(shí) , 給線程指定屬性 pthread_attr_t 是結(jié)構(gòu)體類型*///函數(shù)指針 函數(shù)名 和 &函數(shù)名 都可以作為函數(shù)指針pthread_create( &pid , 0 , threadRun, 0 );II . 線程執(zhí)行函數(shù)
1. 線程執(zhí)行函數(shù)的要求 : C++ 中規(guī)定線程執(zhí)行函數(shù)的函數(shù)指針類型是 void *(PTW32_CDECL *start) (void *) ;
2. 函數(shù)作用 : 將該函數(shù)的指針作為線程創(chuàng)建方法 pthread_create 的第三個(gè)參數(shù) ;
3. 參數(shù)處理 : 在線程創(chuàng)建時(shí) , 傳入?yún)?shù) , 將該參數(shù)轉(zhuǎn)為 char* 字符串指針類型 , 將其打印出來 ;
4. 代碼示例 :
/*定義線程中要執(zhí)行的方法將該函數(shù)的指針作為線程創(chuàng)建方法 pthread_create 的第三個(gè)參數(shù)C++ 中規(guī)定線程執(zhí)行函數(shù)的函數(shù)指針類型是 void *(PTW32_CDECL *start) (void *) */ void* pthread_function(void* args) {//延遲 100 ms 執(zhí)行//_sleep(100);//指針類型轉(zhuǎn)換 : 將 void* 轉(zhuǎn)為 char*// 使用 static_cast 類型轉(zhuǎn)換標(biāo)識(shí)符char* hello = static_cast<char*>(args);//打印參數(shù)cout << "pthread_function 線程方法 執(zhí)行 參數(shù) : " << hello << endl;return 0; }III . 線程方法獲取 Java 對(duì)象
線程方法獲取 Java 對(duì)象步驟 :
① 定義全局變量 jobject obj : 使用該全局變量存儲(chǔ) Java 對(duì)象 ;
//JNI 方法參數(shù)中的第二個(gè)參數(shù) , 需要先將局部變量轉(zhuǎn)為全局變量 , 然后再其它方法中調(diào)用 jobject obj;② JNI 方法處理 : 將 jobject instance 參數(shù) ( 此時(shí)是局部變量 ) 轉(zhuǎn)為 全局變量 , 調(diào)用 NewGlobalRef 方法實(shí)現(xiàn) ;
void threadDemoC(JNIEnv *env, jobject instance){__android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadDemoC");//保存全局變量 , 先將局部變量轉(zhuǎn)為全局變量 , 然后再賦值給全局的 obj 變量// 使用域作用符訪問全局的 ::obj 變量::obj = env->NewGlobalRef(instance);... }這樣就可以在其它方法或其它線程中使用該 Java 對(duì)象了 ;
IV . 線程方法獲取 JNIEnv
線程中獲取 JNIEnv * env 步驟 :
① JNIEnv 無法跨線程 : JNI 方法參數(shù)中的 JNIEnv 指針是不能跨線程使用的 , 在 主線程中調(diào)用 JNI 方法 , 其 JNIEnv 指針不能在子線程中使用 ;
② 獲取途徑 : 如果在子線程中使用 JNIEnv 指針 , 需要使用 JavaVM 獲取 指定線程的 JNIEnv 指針 ;
③ 綁定線程 : 調(diào)用 JavaVM 的 AttachCurrentThread 方法 , 可以綁定線程 , 其傳入一個(gè) JNIEnv ** 二維指針 , 會(huì)返回該線程對(duì)應(yīng)的 JNIEnv 指針 ;
④ 剝離線程 : 注意使用完 JNIEnv 后 , 解綁線程 , 調(diào)用 JavaVM 的 DetachCurrentThread 方法 解綁線程 ;
2 . 代碼示例 :
/*線程執(zhí)行的方法如果在 Native 層執(zhí)行耗時(shí)操作 , 如下載文件 , 需要在線程中處理JNI 方法參數(shù)中的 JNIEnv 指針是不能跨線程使用的 , 在 主線程中調(diào)用 JNI 方法 , 其 JNIEnv 指針不能在子線程中使用如果在子線程中使用 JNIEnv 指針 , 需要使用 JavaVM 獲取 指定線程的 JNIEnv 指針調(diào)用 JavaVM 的 AttachCurrentThread 可以獲取本線程的 JNIEnv 指針注意最后還要將線程從 Java 虛擬機(jī)中剝離關(guān)于參數(shù)傳遞 :傳遞 int 類型 和 int * 類型 , 傳遞指針可以在 方法中修改 int 變量值 ;傳遞 int * 類型 和 int ** 類型 , 傳遞二維指針 可以在方法中修改 int * 一維指針值因此有些參數(shù)需要在方法中修改, 并且需要保存該修改狀態(tài) , 就需要將該變量的地址當(dāng)做參數(shù)傳入原來的普通變量 變成 指針變量 , 一維指針 變 二維指針*/ void* threadRun(void *args){__android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadRun");//JNIEnv 不能跨線程使用 , 這里需要先獲取本線程的 JNIEnvJNIEnv *env;//將線程附加到 Java 虛擬機(jī)中 ( 注意后面對(duì)應(yīng)剝離線程操作 )// 如果成功返回 0 , 如果失敗 , 直接退出int attachResult = _vm->AttachCurrentThread(&env, 0);...//將線程從 Java 虛擬機(jī)中剝離_vm->DetachCurrentThread();//注意這里一定要返回 0 , 否則執(zhí)行到結(jié)尾會(huì)崩潰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");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);threadDemoJava();}/*** JNI 線程 Demo*/public native void threadDemoJava();/*** 打印當(dāng)前線程信息*/public void logThread(){Log.i("JNI_TAG", Thread.currentThread().toString());}}2 . Native 層代碼 :
#include <jni.h> #include <string> #include <android/log.h>//導(dǎo)入線程頭文件 #include <pthread.h>//Java 虛擬機(jī)指針 , 在 JNI_OnLoad 方法中設(shè)置該值 JavaVM *_vm;//JNI 方法參數(shù)中的第二個(gè)參數(shù) , 需要先將局部變量轉(zhuǎn)為全局變量 , 然后再其它方法中調(diào)用 jobject obj;/*線程執(zhí)行的方法如果在 Native 層執(zhí)行耗時(shí)操作 , 如下載文件 , 需要在線程中處理JNI 方法參數(shù)中的 JNIEnv 指針是不能跨線程使用的 , 在 主線程中調(diào)用 JNI 方法 , 其 JNIEnv 指針不能在子線程中使用如果在子線程中使用 JNIEnv 指針 , 需要使用 JavaVM 獲取 指定線程的 JNIEnv 指針調(diào)用 JavaVM 的 AttachCurrentThread 可以獲取本線程的 JNIEnv 指針注意最后還要將線程從 Java 虛擬機(jī)中剝離關(guān)于參數(shù)傳遞 :傳遞 int 類型 和 int * 類型 , 傳遞指針可以在 方法中修改 int 變量值 ;傳遞 int * 類型 和 int ** 類型 , 傳遞二維指針 可以在方法中修改 int * 一維指針值因此有些參數(shù)需要在方法中修改, 并且需要保存該修改狀態(tài) , 就需要將該變量的地址當(dāng)做參數(shù)傳入原來的普通變量 變成 指針變量 , 一維指針 變 二維指針 */ void* threadRun(void *args){__android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadRun");//JNIEnv 不能跨線程使用 , 這里需要先獲取本線程的 JNIEnvJNIEnv *env;//將線程附加到 Java 虛擬機(jī)中 ( 注意后面對(duì)應(yīng)剝離線程操作 )// 如果成功返回 0 , 如果失敗 , 直接退出int attachResult = _vm->AttachCurrentThread(&env, 0);//獲取 MainActivity 對(duì)應(yīng)的 jclass 對(duì)象jclass clazz = env->GetObjectClass( obj );//反射獲取 logThread 方法jmethodID logThreadID = env->GetMethodID(clazz, "logThread", "()V");//調(diào)用 logThread 方法env->CallVoidMethod(obj, logThreadID);//釋放相關(guān)的局部變量env->DeleteLocalRef(clazz);//將線程從 Java 虛擬機(jī)中剝離_vm->DetachCurrentThread();//注意這里一定要返回 0 , 否則執(zhí)行到結(jié)尾會(huì)崩潰return 0;}void threadDemoC(JNIEnv *env, jobject instance){__android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadDemoC");//保存全局變量 , 先將局部變量轉(zhuǎn)為全局變量 , 然后再賦值給全局的 obj 變量// 使用域作用符訪問全局的 ::obj 變量::obj = env->NewGlobalRef(instance);//代表一個(gè)線程的句柄pthread_t pid;//創(chuàng)建線程并執(zhí)行pthread_create( &pid , 0 , threadRun, 0 ); }//下面的代碼是動(dòng)態(tài)注冊(cè)內(nèi)容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 虛擬機(jī)指針_vm = vm;// 2 . 動(dòng)態(tài)注冊(cè)方法//獲取 JNIEnv 指針JNIEnv *env = nullptr;int registerResult = vm->GetEnv( (void **) &env, JNI_VERSION_1_6 );if(registerResult != JNI_OK){return -1;}//進(jìn)行動(dòng)態(tài)注冊(cè)jclass jclazz = env->FindClass(className);env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));return JNI_VERSION_1_6; }3 . 執(zhí)行結(jié)果 :
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]總結(jié)
以上是生活随笔為你收集整理的【Android NDK 开发】JNI 线程 ( JNI 线程创建 | 线程执行函数 | 非 JNI 方法获取 JNIEnv 与 Java 对象 | 线程获取 JNIEnv | 全局变量设置 )的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android NDK 开发】JNI
- 下一篇: 【Android NDK 开发】NDK