当前位置: 首页 > 知识库问答 >
问题:

C回调到Java->当我来自不同的线程时,为什么不能在回调类中检索JNIEnv?

司知
2023-03-14

我试图在Android上构建一个回调类,从我的原生代码中的不同线程调用Java方法。我读过很多关于如何做到这一点的书,只要我在同一条线上,这一切都是有效的。但是,从另一个线程中,我无法正确检索JNIEnv,也无法找出我做错了什么。

我对C和JNI不是很有经验,所以这很可能是初学者的问题。。。但我已经花了好几天时间,看不出它是什么。

这是我的回调类,. 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
    }
}

这是我的原生_库,在这里我获得了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();
}

我缩短了代码,删除了所有不相关/不必要的部分。如果缺少关键的东西,请评论,我会补充的。

解决方案:根据迈克尔在他的回答中提出的建议,我对回调类中的playbackAdvanced方法进行了这些编辑,以使其工作:

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个答案

赵志
2023-03-14

playBackProgress中的逻辑错误。

只有当g_env为非空时,才尝试连接当前线程。但是如果g_env是非空的,那么GetEnv可能成功了(当然,您还应该检查getEnvStat==JNI_OK)并且AttachCurrentThread不是必需的。

g_env为NULL且getEnvStatJNI_EDETACHED时,需要调用attachMONtThread

您还需要跟踪您是否调用了attachMONtThread,因为在这种情况下,您应该在某个时候调用DetachMONtThread。有关更多信息,请参见此答案。

 类似资料:
  • 首先,我决定让我的类阻塞(让消费者更容易使用,但对我来说可能更乏味)。而不是让使用者定义异步回调。这是一个好的设计模式吗?这样,用户可以获得预期的行为,但如果他们对线程被阻塞的时间不满意,则可以实现自己的多线程。 我有一个构造函数,它根据异步回调的结果在类中设置最后一个字段: 这不起作用,所以我使用了原子引用,并实现了一个阻塞循环,直到返回结果,如下所示: 这是阻止/检索结果的好方法吗?

  • 不管什么原因,我的回拨电话打不通。基本上,我将使用我创建的tcp服务器进行群组聊天。我试图在java中创建一个线程,然后使用回调将我从套接字获得的信息提供给主活动。 当前收到得错误: 客户端任务 最后,我想通过响应事件传递一个字符串,它将获得它的主要内容。

  • 问题内容: d3.drag的文档指出拖动事件的DOM元素目标可用于回调: 调度指定事件时,将使用与select相同的上下文和参数调用每个侦听器。在侦听器上:当前数据d和索引i,并且此上下文作为当前DOM元素。 但是我的回叫是一个对象实例,并指向该对象。因此,我需要另一种访问通常传入的当前DOM元素的方法。我该怎么做? 问题答案: 将第二个和第三个参数一起使用以获取何时不可用: 有关详细说明,请看下

  • 问题内容: 我已经阅读了回调的Wikipedia定义,但仍然不明白。谁能解释一下回调是什么,尤其是以下几行 在计算机编程中,回调是对可执行代码或一段可执行代码的引用,该代码作为参数传递给其他代码。这允许较低层的软件层调用较高层中定义的子例程(或函数)。 问题答案: 也许一个例子会有所帮助。 您的应用程序想要从一台远程计算机下载文件,然后写入本地磁盘。远程计算机是拨号调制解调器和卫星链路的另一侧。延

  • 我有一个ViewModel处理我的业务逻辑,我正在使用Koin将它注入到我的活动和每个片段中。然而,在我从片段A-片段B导航并导航回片段A之后,我的观察者再次被触发。为什么会发生这种情况?当我返回时,如何阻止这种onChanged被触发? 我尝试将'this'和'view LifecycleOwner'设置为LiveData的LifecycleOwner。 我还尝试将observable移动到on

  • 每个USB设备都必须根据设备驱动程序将在Linux系统上使用的一些USB设备类定义... 但是我不能理解一些事情。例如,大多数USB调制解调器都属于通信设备类。我有一个3G USB调制解调器和一个3G USB加密狗(例如,塔塔光子),两者都属于相同的通信类别,但3G USB调制解调器使用CDC-ACM驱动程序,3G加密狗使用串行转换器驱动程序(USB-Serial)。是什么让这些设备与众不同? 有