JNI创建线程

作为一个Android开发,或多或少都会接触到JNI,有时候需要创建线程做一些特别的操作。

一、创建线程

使用 pthread 创建线程。

#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,可以发现两条日志的线程不一样:

在这里插入图片描述

二、线程中调用java函数

JNI调用java函数,需用用到java虚拟机环境,也就是JNIEnv指针。pthread_create创建的线程是一个c++中的线程,虚拟机并不能识别他们,为了和java交互,需要把线程附着到java虚拟机上,然后就可以获得当前线程的JNIEnv指针,因为JNIEnv指针只在当前线程中有效。

  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 代码:

public class Test {

  private static final String TAG = "Test";

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

运行后发现程序崩溃了,找不到java的class。

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;
  }
}

运行后日志如下:

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值