Невозможно получить значение JNIEnv * в произвольном контексте

У меня проблема с NDK.

В моем методе JNI_OnLoad я кэширую указатель JavaVm, class, который вызвал метод, и идентификатор метода, который я использую позже:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved){ JNIEnv *env; cachedJVM = jvm; if((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6)){ LOG_ERROR("Could not get JNIEnv*"); return JNI_ERR; } javaClass = (*env)->FindClass(env, "org/test/opensl/AudioProcessor"); if(javaClass == NULL){ LOG_ERROR("Could not get java class"); return JNI_ERR; } javaCallbackMID = (*env)->GetMethodID(env, javaClass, "enqueueAudio", "([B)V"); if(javaCallbackMID == NULL){ LOG_ERROR("Could not get method identifier"); return JNI_ERR; } return JNI_VERSION_1_6; } 

У меня есть небольшой метод полезности, определенный следующим образом, который должен получить указатель на JNIEnv:

 JNIEnv* JNU_GetEnv(){ JNIEnv* env; (*cachedJVM)->GetEnv(cachedJVM, (void**)&env, JNI_VERSION_1_6); return env; } 

И, наконец, у меня есть обратный вызов из OpenSL ES SLAndroidSimpleBufferQueueItf который я хочу обработать записанным звуком из SLRecordItf :

 void recorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context){ SLresult result; JNIEnv* env; recorderContext* thisContext = (recorderContext*)context; env = JNU_GetEnv(); if(env == NULL){ LOG_ERROR("Could not get JNIEnv*"); return; } jbyteArray data = (*env)->NewByteArray(env, MAX_PACKET_SIZE); if(data == NULL){ LOG_ERROR("No memory could be allocated for buffer"); return; } (*env)->SetByteArrayRegion(env, data, 0, MAX_PACKET_SIZE, recorderBuffer); (*env)->CallByteMethodA(env, thisContext->caller, javaCallbackMID, data); (*env)->DeleteLocalRef(env, data); result = (*bq)->Enqueue(bq, recorderBuffer, RECORDER_FRAMES * sizeof(jbyte)); checkError(result, "Unable to enqueue new buffer"); } 

Если параметр контекста для метода обратного вызова содержит только ссылку на объект, который вызвал собственный метод. Это самоопределяемая структура:

 typedef struct recorderContext{ jobject caller; } recorderContext; 

Однако каждый раз, когда я пытаюсь запустить это, я получаю сообщение об ошибке "Could not get JNIEnv*" из метода обратного вызова.

Мой вопрос в основном сводится к следующему: почему я могу получить указатель на JNIEnv в методе JNI_OnLoad, но не в recorderCallback, поскольку оба используют один и тот же указатель Java VM для получения JNIEnv?

Мне нужен этот обратный вызов, чтобы передать записанное аудио обратно на мой уровень Java для дальнейшей обработки …

Почему я могу получить указатель на JNIEnv в методе JNI_OnLoad, но не в recorderCallback, так как оба используют один и тот же указатель Java VM для получения JNIEnv?

Поскольку обратный вызов происходит в каком-то собственном streamе, отличном от streamа VM, который загружает библиотеку. Реализация JNI поддерживает JNIEnv для streamа и помещает указатель в локальное хранилище streamов. Он инициализируется только для собственных streamов, подключенных к виртуальной машине. Вам нужно вызвать AttachCurrentThread() (или, вернее, AttachCurrentThreadAsDaemon() ) внутри обратного вызова, чтобы получить указатель JNIEnv действительный для этого streamа. Это присоединяет stream к виртуальной машине при первом вызове и после этого становится nop.

См. http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html.

Caveat: Этот ответ предполагает правильную Java. Проблемы, которые вы видите, показывают, что Dalvik ведет себя так же, как JVM.