JavaVM、JNIEnv和jobject的理解
三者特点:
1)JavaVM:能够跨越线程,能够跨越函数;
2)JNIEnv:不能跨越线程,否则奔溃,可以跨越函数;
3)jobject:不能跨越线程,否则奔溃,不能跨越函数,否则奔溃。
代码验证
1)java层native函数定义、在主线程和子线程调用验证
/**
* java层主线程调用
*/
public native void nativeFun0();
public native void nativeFun1();
/**
* java层子线程调用
*/
public native void nativeFun2();
/**
* 按钮点击事件
*/
public void clickMethod0(View view) {
nativeFun0();
}
public void clickMethod(View view) {
nativeFun1();
}
public void clickMethod2(View view) {
new Thread() {
@Override
public void run() {
super.run();
nativeFun2(); // Java的子线程调用
}
}.start();
}
2)native层函数实现和native层函数子线程实现
// 定义全局javaVM,用于多线程使用
JavaVM *java_vm = nullptr;
// 重写JNI_OnLoad函数,在执行System.loadLibrary时,调用该函数,在此进行动态注册
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *javaVm, void *) {
::java_vm = javaVm;
return JNI_VERSION_1_6;
}
/**
* 在Java层主线程调用nativeFun0
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_thread_MainActivity_nativeFun0(JNIEnv *env, jobject thiz) {
JavaVM *javaVm = nullptr;
env->GetJavaVM(&javaVm);
// 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址, JNI_OnLoad的jvm地址
LOGE("Java层主线程 nativeFun0 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n",
env, javaVm, thiz, ::java_vm);
}
/**
* 定义子线程的函数指针
*/
void *run(void *) {
JNIEnv *jniEnv = nullptr;
// 在子线程附件一个jniEnv
::java_vm->AttachCurrentThread(&jniEnv, nullptr);
LOGE("native层子线程 run jvm地址:%p, 当前run函数的newEnv地址:%p \n", ::java_vm, jniEnv);
// 释放
::java_vm->DetachCurrentThread();
return nullptr;
}
/**
* 在Java层主线程调用nativeFun1,native层调用子线程的函数指针
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_thread_MainActivity_nativeFun1(JNIEnv *env, jobject thiz) {
JavaVM *javaVm = nullptr;
env->GetJavaVM(&javaVm);
// 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址, JNI_OnLoad的jvm地址
LOGE("Java层主线程 nativeFun1 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n",
env,
javaVm, thiz, ::java_vm);
// 创建子线程
pthread_t pid;
pthread_create(&pid, nullptr, run, nullptr);
}
/**
* 在Java层子线程调用nativeFun2
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_thread_MainActivity_nativeFun2(JNIEnv *env, jobject thiz) {
JavaVM *javaVm = nullptr;
env->GetJavaVM(&javaVm);
// 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址, JNI_OnLoad的jvm地址
LOGE("Java层子线程 nativeFun2 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n",
env,
javaVm, thiz, ::java_vm);
}
小结
查看打印log分析
1)所有的JavaVM地址都是相同的,即JavaVM为同一个;
2)主线程的JNIEnv地址是相同,子线程的JNIEnv地址是不相同;
3)主线程的jobject地址是相同,子线程的jobject地址跟主线程是不相同;
结论:
1. JavaVM全局,绑定当前进程, 只有一个地址
2. JNIEnv线程绑定, 绑定主线程,主线程相同,故主线程地址相同;绑定子线程,多个子线程,故子线程地址不相同。
3. jobject 谁调用JNI函数,谁的实例会给jobject,如:MainActivity主线程调用地址指向MainActivity;子线程Thread程调用地址指向子线程Thread。
JNIEnv和jobject的崩溃解决:
JNIEnv:
使用全局的JavaVM附加当前异步线程 得到权限env操作。
JNIEnv *jniEnv = nullptr;
// 在子线程附件一个jniEnv
::java_vm->AttachCurrentThread(&jniEnv, nullptr);
// 释放
::java_vm->DetachCurrentThread();
jobject:
默认是局部引用,提升全局引用,可解决此问题。
env->NewGlobalRef(jobject); // 提升全局引用