JNI建立線程

做爲一個Android開發,或多或少都會接觸到JNI,有時候須要建立線程作一些特別的操做。java

1、建立線程

使用 pthread 建立線程。android

#include <jni.h>
#include <android/log.h>
//添加頭文件
#include <pthread.h>

#define LOG_TAG "nativethread"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

static void *test(void *data) {
  LOGI("test");
  //必須加這行代碼,不然會直接崩潰
  return nullptr;
}

void createThread() {
  pthread_t thread;
  /** * 四個參數: * 1. 指向線程標識符的指針 * 2. 設置線程屬性 * 3. 線程運行函數的起始地址 * 4. 運行函數的參數 */
  int result = pthread_create(&thread, nullptr, test, nullptr);
  if (result != 0) {
    LOGE("線程啓動失敗");
  }
}

extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  LOGI("JNI load---------");

  createThread();

  return JNI_VERSION_1_6;
}
複製代碼

查看logcat,能夠發現兩條日誌的線程不同:c++

日誌打印.png

2、線程中調用java函數

JNI調用java函數,需用用到java虛擬機環境,也就是JNIEnv指針。pthread_create建立的線程是一個c++中的線程,虛擬機並不能識別他們,爲了和java交互,須要把線程附着到java虛擬機上,而後就能夠得到當前線程的JNIEnv指針,由於JNIEnv指針只在當前線程中有效。markdown

  1. 經過 AttachCurrentThread 方法能夠將當前線程附着到 Java 虛擬機上,而且能夠得到 JNIEnv 指針。
  2. AttachCurrentThread 方法是由 JavaVM 指針調用的,它表明的是 Java 虛擬機接口指針,能夠在 JNI_OnLoad 加載時來得到,經過全局變量保存起來。
  3. 當經過 AttachCurrentThread 方法將線程附着當 Java 虛擬機上後,還須要將該線程從 Java 虛擬機上分離,經過 DetachCurrentThread 方法,這兩個方法是要同時使用的,不然會帶來 BUG 。

具體代碼以下:函數

#include <jni.h>
#include <android/log.h>
#include <pthread.h>

#define LOG_TAG "nativethread"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

static JavaVM *gJavaVM;

static void printLog(JNIEnv *env, char* msg) {
  //調用java方法打印日誌
  jclass test = env->FindClass("com/francis/test/nativethread/Test");
  jmethodID method = env->GetStaticMethodID(test, "printLog","(Ljava/lang/String;)V");
  env->CallStaticVoidMethod(test, method, env->NewStringUTF(msg));
}

static void *test(void *data) {
  LOGI("test");
  int status;
  JNIEnv *env;
  bool isAttached = false;

  status = gJavaVM->GetEnv((void **)(&env), JNI_VERSION_1_6);
  if (status == JNI_EDETACHED) {
    //將當前線程附着在java虛擬機上
    status = gJavaVM->AttachCurrentThread(&env, nullptr);
    if (status != JNI_OK) {
      LOGE("Failed to attach current thread");
      return nullptr;
    }

    isAttached = true;
  }
  
  printLog(env, "new Thread");

  if(isAttached) {
    //將當前線程從java虛擬機上分離
    gJavaVM->DetachCurrentThread();
  }
  return nullptr;
}

void createThread() {
  pthread_t thread;
  
  int result = pthread_create(&thread, nullptr, test, nullptr);
  if (result != 0) {
    LOGE("線程啓動失敗");
  }
}

extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  LOGI("JNI load---------");

  JNIEnv *env;
  if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
    return JNI_ERR;
  }
  //保存全局變量
  gJavaVM = vm;

  printLog(env, "JNI_OnLoad");

  createThread();

  return JNI_VERSION_1_6;
}
複製代碼

java 代碼:ui

public class Test {

  private static final String TAG = "Test";

  public static void printLog(String msg) {
    Log.d(TAG, "printLog: " + msg);
  }
}
複製代碼

運行後發現程序崩潰了,找不到java的class。spa

java.lang.ClassNotFoundException: Didn't find class "com.francis.test.nativethread.Test" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
複製代碼

class也須要聲明成全局的變量線程

#include <jni.h>
#include <android/log.h>
#include <pthread.h>

#define LOG_TAG "nativethread"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

static JavaVM *gJavaVM;
jclass testClass;

static void printLog(JNIEnv *env, char* msg) {
  //調用java方法打印日誌
  //jclass test = env->FindClass("com/francis/test/nativethread/Test");
  jmethodID method = env->GetStaticMethodID(testClass, "printLog","(Ljava/lang/String;)V");
  env->CallStaticVoidMethod(testClass, method, env->NewStringUTF(msg));
}

static void *test(void *data) {
  LOGI("test");
  int status;
  JNIEnv *env;
  bool isAttached = false;

  status = gJavaVM->GetEnv((void **)(&env), JNI_VERSION_1_6);
  if (status == JNI_EDETACHED) {
    //將當前線程附着在java虛擬機上
    status = gJavaVM->AttachCurrentThread(&env, nullptr);
    if (status != JNI_OK) {
      LOGE("Failed to attach current thread");
      return nullptr;
    }

    isAttached = true;
  }
  
  printLog(env, "new Thread");

  if(isAttached) {
    //將當前線程從java虛擬機上分離
    gJavaVM->DetachCurrentThread();
  }
  return nullptr;
}

void createThread() {
  pthread_t thread;
  
  int result = pthread_create(&thread, nullptr, test, nullptr);
  if (result != 0) {
    LOGE("線程啓動失敗");
  }
}

extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  LOGI("JNI load---------");

  JNIEnv *env;
  if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
    return JNI_ERR;
  }
  //保存全局變量
  gJavaVM = vm;
  
  //建立全局引用
  jclass test = env->FindClass("com/francis/test/nativethread/Test");
  testClass = (jclass)(env->NewGlobalRef(test));
  env->DeleteLocalRef(test);

  printLog(env, "JNI_OnLoad");

  createThread();

  return JNI_VERSION_1_6;
}

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
  JNIEnv *env;
  if (vm->GetEnv((void **)(&env), JNI_VERSION_1_6) != JNI_OK) {
    return;
  }

  //刪除全局變量
  if (testClass != nullptr) {
    env->DeleteGlobalRef(testClass);
    testClass = nullptr;
  }
}
複製代碼

運行後日志以下:指針

日誌打印2.png

相關文章
相關標籤/搜索