JavaVM与JNIEnv
上一章简单测试了一下Java的反射,这章我们来测试一下JavaVM和JNIEnv
回顾一下Java反射
在我们使用NDK访问java类中的方法的时候,流程也是差不多一样的
找到类名->jFieldID/jmethodID(等于是Java当中的GetField)->通过GetObjectField获取到Java类中的函数或值
在调用函数的时候也要注意,如果类中没有同名函数可以不用传递参数。如果存在同名函数就需要将参数一并传递
函数描述符
JavaVM和JNIEnv
JavaVM结构体里面包含了5个方法
JNIEnv结构体里面包含了非常多的方法可以调用
怎么获取JavaVM和JNIEnv
第一种方法:在JNI_OnLoad中获取
globalVM = vm;
__android_log_print(4, "xiaoeryu->jni", "JNI_OnLoad(JavaVM* vm, void* reserved)->%p", vm);
__android_log_print(4, "xiaoeryu->jni", "jni->%s", "JNI_OnLoad is called");
jint result = 0;
JNIEnv *env = nullptr;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) == JNI_OK){
__android_log_print(4, "xiaoeryu->jni", "jni->%s", "vm->GetEnv((void **)&env, JNI_VERSION_1_6) success");
}
__android_log_print(4, "xiaoeryu->jni", "GetEnv((void **)&env, JNI_VERSION_1_6)->%p", env);
第二种方法:通过JNI函数的传参获取
第三种方法:在子线程中获取
// 在子线程中调用JNIEnv的接口
JNIEnv *threadenv = nullptr;
globalVM->AttachCurrentThread(&threadenv, nullptr);
if (globalVM->GetEnv((void **)&threadenv, JNI_VERSION_1_6) == JNI_OK){
__android_log_print(4, "xiaoeryu->jni", "jni->%s", "(globalVM->GetEnv((void **)&threadenv, JNI_VERSION_1_6) success");
jstring jstring1 = threadenv->NewStringUTF("threadtest jstring");
const char* content = threadenv->GetStringUTFChars(jstring1, nullptr);
__android_log_print(4, "xiaoeryu->jni", "jni->%s", content);
threadenv->ReleaseStringUTFChars(jstring1, content);
} else{
__android_log_print(4, "xiaoeryu->jni", "jni->%s", "(globalVM->GetEnv((void **)&threadenv, JNI_VERSION_1_6) failed");
}
JNIEnv是与线程相关的,每一个线程都有自己的env
native子线程无法加载app自己的Class
通过pthread_create之类的方法在native层创建了子线程,则在这个子线程中FindClass方法查不到我们Apk中定义的class。会返回0并且在Java层抛出ClassNotFoundException:
因为在子线程中使用
FindClass
方法,他将会使用与当前线程关联的类加载器进行类查找。就可能会导致子线程中无法找到在主线程中加载的类。
通过Exception可以捕获到具体的异常
// jclass TestJclass = threadenv->FindClass("com/xiaoeryu/reflectiontest/Test");
// threadenv->ExceptionDescribe();
// threadenv->ExceptionClear();
解决办法
这里使用一种通用的解决办法:
通过在主线程中使用FindClass
找到类的jclass
对象,然后将这个对象传递给子线程使用。
举个栗子
// 在主线程中
jclass TestJclass = env->FindClass("com/xiaoeryu/reflectiontest/Test");
jobject appClassloader = env->NewGlobalRef(TestJclass);
// 在子线程中
void *threadtest(void* args){
JNIEnv *threadenv = nullptr;
globalVM->AttachCurrentThread(&threadenv, nullptr);
jclass TestJclass = (jclass)args; // 使用主线程中找到的类引用
// 继续使用 TestJclass 进行其他 JNI 操作
globalVM->DetachCurrentThread();
pthread_exit(0);
}
// 创建子线程时传递类的引用
pthread_t thread;
pthread_create(&thread, nullptr, threadtest, appClassloader);
pthread_join(thread, nullptr);
// 在主线程中删除全局引用
env->DeleteGlobalRef(appClassloader);
当然在类外定义一个全局变量,在主类中赋值然后再子类中使用也是可以的,但是使用
NewGlobalRef
更灵活、更方便控制。
- 标题: JavaVM与JNIEnv
- 作者: xiaoeryu
- 创建于 : 2023-10-12 01:19:06
- 更新于 : 2023-10-14 00:28:32
- 链接: https://github.com/xiaoeryu/2023/10/12/JavaVM与JNIEnv/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论