Обратный вызов C ++ на Java -> Почему я не могу получить JNIEnv в классе обратного вызова, когда приходит из другого потока? - PullRequest
0 голосов
/ 19 апреля 2020

Я пытаюсь создать класс обратного вызова для вызова метода Java из разных потоков в моем нативном коде на Android. Я много читал о том, как это сделать, и пока я в одной ветке, все это работает. Но из другого потока я не могу получить JNIEnv правильно и не могу понять, что я делаю неправильно.

Я не очень разбираюсь в C ++ и JNI, так что это довольно возможно, это какая-то проблема новичка ... но я потратил несколько дней и не могу понять, что это такое.

Это мой класс обратного вызова, .h и. cpp file:

class AudioCallback {

public:
   explicit AudioCallback(JavaVM&, jobject);
    void playBackProgress(int progressPercentage);

private:
    JavaVM& g_jvm;
    jobject g_object;
};
jclass target = NULL;
jmethodID id = NULL;

AudioCallback::AudioCallback(JavaVM &jvm, jobject object) : g_jvm(jvm), g_object(object) {
    JNIEnv *g_env;
    int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);

    if (g_env != NULL) {
        target = g_env->GetObjectClass(g_object);
        id = g_env->GetMethodID(target, "integerCallback", "(I)V");

        //This is a test call to see if I can call my java method. It works.
        g_env->CallVoidMethod(g_object, id, (jint) 103);
    }
}

// this method is calles from other threads, so I want to attach to the current thread once I got my JNIEnv, but I can't since it's null...
void AudioCallback::playBackProgress(int progressPercentage) {
    JNIEnv *g_env;

    // This is null and I don't know why!
    int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);


    if (g_env == NULL) {
        LOGE("JNIEnv in callback method is null");
    } else {
        LOGD("Env Stat: %d", getEnvStat);
        JavaVMAttachArgs vmAttachArgs;
        if (getEnvStat == JNI_EDETACHED) {
            LOGD("GetEnv: not attached - attaching");
            if (g_jvm.AttachCurrentThread(&g_env, &vmAttachArgs) != 0) {
                LOGD("GetEnv: Failed to attach");
            }
        } else if (getEnvStat == JNI_OK) {
            LOGD("GetEnv: JNI_OK");
        } else if (getEnvStat == JNI_EVERSION) {
            LOGD("GetEnv: version not supported");
        }
        g_env->CallVoidMethod(g_object, id, (jint) progressPercentage);
        //thread gets detached elsewhere
    }
}

Это мой native_lib, где я получаю JavaVM и создаю экземпляр класса обратного вызова:

std::unique_ptr<AudioEngine> audioEngine;
std::unique_ptr<AudioCallback> callback;

JavaVM *g_jvm = nullptr;

static jobject myJNIClass;

jint JNI_OnLoad(JavaVM *pJvm, void *reserved) {
    g_Jvm = pJvm;
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNICALL
Java_com_my_appy_common_jni_JniBridge_playFromJNI(JNIEnv *env, jobject instance,jstring URI) {

    myJNIClass = env->NewGlobalRef(instance);

    callback = std::make_unique<AudioCallback>(*gJvm, myJNIClass);

    // this test call to my callback works
    callback->playBackProgress(104);

    const char *uri = env->GetStringUTFChars(URI, NULL);

    //... urelated code is left out here ...

    //audioEngine gets the callback and uses it from threads it creates
    audioEngine = std::make_unique<AudioEngine>(*extractor, *callback);
    audioEngine->setFileName(uri);
    audioEngine->start();
}

Я сократил код и удалил все несвязанные / ненужные части. Если что-то важное отсутствует, пожалуйста, прокомментируйте, и я добавлю это.

Решение: Согласно предложениям, которые @Michael сделал в своем ответе, я внес эти изменения в метод playbackProgress в своем обратном вызове. класс, чтобы заставить его работать:

void AudioCallback::playBackProgress(int progressPercentage) {
    JNIEnv *g_env;
    int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);

    if (getEnvStat == JNI_EDETACHED) {
        LOGD("GetEnv: not attached - attaching");
        if (g_jvm.AttachCurrentThread(&g_env, NULL) != 0) {
            LOGD("GetEnv: Failed to attach");
        }
    } else if (getEnvStat == JNI_OK) {
        LOGD("GetEnv: JNI_OK");
    } else if (getEnvStat == JNI_EVERSION) {
        LOGD("GetEnv: version not supported");
    }
    g_env->CallVoidMethod(g_object, id, (jint) progressPercentage);
//    mJvm.DetachCurrentThread();
}

Теперь он проверяет значение getEnvStat напрямую, нулевая проверка g_env перед ошибкой. Мне также пришлось заменить JavaVMAttachArgs на NULL, чтобы он заработал.

1 Ответ

1 голос
/ 19 апреля 2020

Ваш лог c в playBackProgress неверен.

Единственный раз, когда вы пытаетесь присоединить текущий поток, это когда g_env не равен NULL. Но если g_env не равен NULL, то GetEnv, вероятно, успешно (вы, конечно, также должны убедиться, что getEnvStat == JNI_OK) и AttachCurrentThread не требуется.

Случай, в котором вам нужно вызвать AttachCurrentThread - это когда g_env равен NULL, а getEnvStat равен JNI_EDETACHED.

Вам также необходимо отслеживать, действительно ли вы звонили AttachCurrentThread, поскольку в какой-то момент вам следует звонить DetachCurrentThread в тех случаях. См. этот ответ для получения дополнительной информации об этом.

...