JNI线程绑定的普通用法
JNIEnv *env = nullptr;
jvm->AttachCurrentThread(&env, nullptr);
env->CallVoidMethod(obj, onErrorMethod, -1);
jvm->DetachCurrentThread();
普通用法的缺陷
如果子线程是在C++中创建的,然后通过以上代码来执行Java回调,则没有任何问题
但如果子线程是在Java代码中创建的,Java调用了C++代码,C++代码再调用Java回调方法,就会有问题
因为C++在调用DetachCurrentThread时,这个方法本身就是运行在Java代码之内的,Java仍然在使用此线程,所以是不可能完成解绑操作的
更完善的做法时,在AttachCurrentThread和DetachCurrentThread前,先判断当前代码块,是否已经和Java线程的运行环境绑定,如果已绑定,则没必要再次AttachCurrentThread或DetachCurrentThread
如果我们不这样做,代码运行时则可能会抛出以下的错误
attempting to detach while still running code
正确的使用姿势
JNIEnv *env = nullptr;
bool detached = jvm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_EDETACHED;
if (detached) jvm->AttachCurrentThread(&env, nullptr);
env->CallVoidMethod(obj, onErrorMethod, -1);
if (detached) jvm->DetachCurrentThread();
JNIEnv工作原理
JNIEnv封装了JNI在某个线程中的运行环境
当Java加载JNI接口,或者调用C++函数时,会将自己的线程信息通过JNIEnv传递给C++
通过GetEnv,我们可以在C++中获取对应的Java线程运行环境
如果当前C++线程,已经和Java线程运行环境绑定,则将该运行环境的地址赋值给JNIEnv*,否则JNIEnv*置为NULL
如果GetEnv得到的JNIEnv*为NULL,则说明该线程环境一定是由C++代码创建的,没有和Java运行环境绑定
此时我们如果希望Java代码也运行在C++线程中,就需要手动AttachCurrentThread
每个JNIEnv对应一个固定的线程,在调用DetachCurrentThread之前,这个JNIEnv是一直可用的,并且它的代码会一直运行在原先的线程
DetachCurrentThread之后,对应的JNIEnv也会失效,再使用就会报错
对于同一个C++线程,我们DetachCurrentThread后,再AttachCurrentThread,还是会返回原先的JNIEnv,并且继续可用