C++ 回调 Java -> 为什么我无法在来自不同线程的回调 class 中检索 JNIEnv?
C++ Callback to Java -> Why can't I retrieve JNIEnv in callback class when coming from a different thread?
我正在尝试构建回调 class 以在 Android 上从我的本机代码中的不同线程调用 Java 方法。我已经阅读了很多关于如何做到这一点的内容,只要我在同一个线程上,它就可以正常工作。但是从另一个线程我无法正确检索 JNIEnv
,我也不知道我做错了什么。
我对 C++ 和 JNI 不是很有经验,所以这很可能是一些初学者的问题...但我花了好几天时间,看不出它是什么。
这是我的回调 class,.h 和 .cpp 文件:
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
并实例化回调 class:
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();
}
我已经缩短了代码并删除了所有 unrelated/unnecessary 部分。如果有什么重要的遗漏,请评论,我会添加它。
解决方案:根据@Michael 在他的回答中提出的建议,我在回调 class 中对 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
才能使其正常工作。
你的逻辑playBackProgress
错了。
您唯一一次尝试附加当前线程是在 g_env
为非 NULL 时。但是如果 g_env
是非 NULL 那么 GetEnv
可能成功了(你当然也应该检查 getEnvStat == JNI_OK
)并且 AttachCurrentThread
不是必需的。
需要调用AttachCurrentThread
的情况是g_env
为NULL,getEnvStat
为JNI_EDETACHED
。
您还需要跟踪是否确实调用了 AttachCurrentThread
,因为在这些情况下您应该在某个时候调用 DetachCurrentThread
。有关详细信息,请参阅 。
我正在尝试构建回调 class 以在 Android 上从我的本机代码中的不同线程调用 Java 方法。我已经阅读了很多关于如何做到这一点的内容,只要我在同一个线程上,它就可以正常工作。但是从另一个线程我无法正确检索 JNIEnv
,我也不知道我做错了什么。
我对 C++ 和 JNI 不是很有经验,所以这很可能是一些初学者的问题...但我花了好几天时间,看不出它是什么。
这是我的回调 class,.h 和 .cpp 文件:
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
并实例化回调 class:
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();
}
我已经缩短了代码并删除了所有 unrelated/unnecessary 部分。如果有什么重要的遗漏,请评论,我会添加它。
解决方案:根据@Michael 在他的回答中提出的建议,我在回调 class 中对 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
才能使其正常工作。
你的逻辑playBackProgress
错了。
您唯一一次尝试附加当前线程是在 g_env
为非 NULL 时。但是如果 g_env
是非 NULL 那么 GetEnv
可能成功了(你当然也应该检查 getEnvStat == JNI_OK
)并且 AttachCurrentThread
不是必需的。
需要调用AttachCurrentThread
的情况是g_env
为NULL,getEnvStat
为JNI_EDETACHED
。
您还需要跟踪是否确实调用了 AttachCurrentThread
,因为在这些情况下您应该在某个时候调用 DetachCurrentThread
。有关详细信息,请参阅