作为一个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指针只在当前线程中有效。
- 通过 AttachCurrentThread 方法可以将当前线程附着到 Java 虚拟机上,并且可以获得 JNIEnv 指针。
- AttachCurrentThread 方法是由 JavaVM 指针调用的,它代表的是 Java 虚拟机接口指针,可以在 JNI_OnLoad 加载时来获得,通过全局变量保存起来。
- 当通过 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;
}
}
运行后日志如下: