JavaVM与JNIEnv

xiaoeryu Lv5

上一章简单测试了一下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 进行许可。
评论