【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 不能跨線程使用 , 這里需要先獲取本線程的 JNIEnvJNIEnv *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");}@Overrideprotected 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 不能跨線程使用 , 這里需要先獲取本線程的 JNIEnvJNIEnv *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]總結
以上是生活随笔為你收集整理的【Android NDK 开发】JNI 线程 ( JNI 线程创建 | 线程执行函数 | 非 JNI 方法获取 JNIEnv 与 Java 对象 | 线程获取 JNIEnv | 全局变量设置 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android NDK 开发】JNI
- 下一篇: 【Android NDK 开发】NDK