关于android:无法在任意上下文中获取JNIEnv *值

Unable to get JNIEnv* value in arbitrary context

我对NDK有问题。

在我的JNI_OnLoad方法中,我缓存了JavaVm指针,调用该方法的类以及稍后将在以下方法上使用的方法ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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的指针:

1
2
3
4
5
JNIEnv* JNU_GetEnv(){
    JNIEnv* env;
    (*cachedJVM)->GetEnv(cachedJVM, (void**)&env, JNI_VERSION_1_6);
    return env;
}

最后,我有一个来自OpenSL ES SLAndroidSimpleBufferQueueItf的回调,我想处理来自SLRecordItf的录制音频:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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");
}

其中回调方法的上下文参数仅保留对调用本机方法的对象的引用。这是一个自定义的结构,如下所示:

1
2
3
typedef struct recorderContext{
    jobject caller;
} recorderContext;

但是,每次我尝试运行此命令时,都会从回调方法中收到"Could not get JNIEnv*"错误消息。

我的问题基本上可以归结为:为什么我可以在JNI_OnLoad方法中获得指向JNIEnv的指针,而在recorderCallback中却不能,因为两者都使用相同的Java VM指针来获取JNIEnv?

我需要此回调以将记录的音频传递回我的Java层以进行进一步处理...


Why can I get a pointer to the JNIEnv
in the JNI_OnLoad method, but not in
the recorderCallback, as both use the
same Java VM pointer to get the
JNIEnv?

因为回调发生在某些本机线程上,与加载库的VM线程不同。 JNI实现为每个线程维护一个JNIEnv,并将指针放在线程本地存储中。它仅针对连接到VM的本机线程初始化。您需要在回调内部调用AttachCurrentThread()(或更可能是AttachCurrentThreadAsDaemon()),以获取对该线程有效的JNIEnv指针。这会在第一次调用时将线程附加到VM,之后是nop。

请参阅http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html

注意事项:此答案假定使用正确的Java。您看到的问题表明Dalvik的行为与JVM相同。