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

拯救JNIEnv最好的方法是什么*

司空思聪
2023-03-14

我和JNI有一个Android项目。在实现侦听器类的CPP文件中,有一个回调x()。调用x()函数时,我想调用java类中的另一个函数。然而,为了调用该java函数,我需要访问JNIEnv*。

我知道在回调的同一个cpp文件中,有一个函数:

static jboolean init (JNIEnv* env, jobject obj) {...}

init(..)时,我应该将JNIEnv*作为成员变量保存在cpp文件中吗 被调用了吗?然后在回调发生时使用它?

抱歉,我是JNI的初学者。


共有2个答案

倪举
2023-03-14

@Michael很好地概述了如何通过缓存JVM来检索JNI。对于那些不想使用pthread(或者因为你在Windows系统上而不能使用pthread),并且你正在使用c 11或更高版本的系统,那么线程本地存储就是一种选择。

下面是一个关于如何实现一个包装器方法的粗略示例,该方法可以正确地连接到线程,并在线程退出时自动清理

JNIEnv* JNIThreadHelper::GetJniEnv() {

    // This method might have been called from a different thread than the one that created
    // this handler. Check to make sure that the JNI is attached and if not attach it to the 
    // new thread.

    // double check it's all ok
    int nEnvStat = m_pJvm->GetEnv(reinterpret_cast<void**>(&m_pJniEnv), JNI_VERSION_1_6);

    if (nEnvStat == JNI_EDETACHED) {

        std::cout << "GetEnv: not attached. Attempting to attach" << std::endl;

        JavaVMAttachArgs args;
        args.version = JNI_VERSION_1_6; // choose your JNI version
        args.name = NULL; // you might want to give the java thread a name
        args.group = NULL; // you might want to assign the java thread to a ThreadGroup

        if (m_pJvm->AttachCurrentThread(&m_pJniEnv, &args) != 0) {
            std::cout << "Failed to attach" << std::endl;
            return nullptr;
        }

        thread_local struct DetachJniOnExit {
            ~DetachJniOnExit() {
                m_pJvm->DetachCurrentThread();
            }
        };


        m_bIsAttachedOnAThread = true;

    }
    else if (nEnvStat == JNI_OK) {
        //
    }
    else if (nEnvStat == JNI_EVERSION) {

        std::cout << "GetEnv: version not supported" << std::endl;
        return nullptr;
    }


    return m_pJniEnv;
}
宰修能
2023-03-14

缓存一个JNIEnv*并不是一个特别好的主意,因为您不能跨多个线程使用同一个JNIEnv*,甚至可能无法在同一线程上使用它进行多个本机调用(请参阅http://android-developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html)

编写一个函数来获取JNIEnv*,并在必要时将当前线程附加到VM,这并不太困难:

bool GetJniEnv(JavaVM *vm, JNIEnv **env) {
    bool did_attach_thread = false;
    *env = nullptr;
    // Check if the current thread is attached to the VM
    auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (vm->AttachCurrentThread(env, NULL) == JNI_OK) {
            did_attach_thread = true;
        } else {
            // Failed to attach thread. Throw an exception if you want to.
        }
    } else if (get_env_result == JNI_EVERSION) {
        // Unsupported JNI version. Throw an exception if you want to.
    }
    return did_attach_thread;
}

您使用它的方式是:

JNIEnv *env;
bool did_attach = GetJniEnv(vm, &env);
// Use env...
// ...
if (did_attach) {
   vm->DetachCurrentThread();
}

你可以把它包装成一个类,在构建时附加,在破坏时分离,RAII风格:

class ScopedEnv {
public:
    ScopedEnv() : attached_to_vm_(false) {
        attached_to_vm_ = GetJniEnv(g_vm, &env_);  // g_vm is a global
    }

    ScopedEnv(const ScopedEnv&) = delete;
    ScopedEnv& operator=(const ScopedEnv&) = delete;

    virtual ~ScopedEnv() {
        if (attached_to_vm_) {
            g_vm->DetachCurrentThread();
            attached_to_vm_ = false;
        }
    }

    JNIEnv *GetEnv() const { return env_; }

private:
    bool attached_to_env_;
    JNIEnv *env_;
};

// Usage:

{
    ScopedEnv scoped_env;
    scoped_env.GetEnv()->SomeJniFunction();
}
// scoped_env falls out of scope, the thread is automatically detached if necessary

编辑:有时您可能有一个运行时间较长的本地线程,在许多情况下需要一个JNIEnv*。在这种情况下,您可能希望避免不断地将线程连接到/从JVM分离,但是您仍然需要确保在线程销毁时分离线程。

您可以通过只附加一次线程,然后将其保持在附加状态,并使用pthread_key_createpthread_setspecific设置线程销毁回调来实现这一点,该回调将负责调用DetachCurrentThread

/**
 * Get a JNIEnv* valid for this thread, regardless of whether
 * we're on a native thread or a Java thread.
 * If the calling thread is not currently attached to the JVM
 * it will be attached, and then automatically detached when the
 * thread is destroyed.
 */   
JNIEnv *GetJniEnv() {
    JNIEnv *env = nullptr;
    // We still call GetEnv first to detect if the thread already
    // is attached. This is done to avoid setting up a DetachCurrentThread
    // call on a Java thread.

    // g_vm is a global.
    auto get_env_result = g_vm->GetEnv((void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (g_vm->AttachCurrentThread(&env, NULL) == JNI_OK) {
            DeferThreadDetach(env);
        } else {
            // Failed to attach thread. Throw an exception if you want to.
        }
    } else if (get_env_result == JNI_EVERSION) {
        // Unsupported JNI version. Throw an exception if you want to.
    }
    return env;
}

void DeferThreadDetach(JNIEnv *env) {
    static pthread_key_t thread_key;

    // Set up a Thread Specific Data key, and a callback that
    // will be executed when a thread is destroyed.
    // This is only done once, across all threads, and the value
    // associated with the key for any given thread will initially
    // be NULL.
    static auto run_once = [] {
        const auto err = pthread_key_create(&thread_key, [] (void *ts_env) {
            if (ts_env) {
                g_vm->DetachCurrentThread();
            }
        });
        if (err) {
            // Failed to create TSD key. Throw an exception if you want to.
        }
        return 0;
    }();

    // For the callback to actually be executed when a thread exits
    // we need to associate a non-NULL value with the key on that thread.
    // We can use the JNIEnv* as that value.
    const auto ts_env = pthread_getspecific(thread_key);
    if (!ts_env) {
        if (pthread_setspecific(thread_key, env)) {
            // Failed to set thread-specific value for key. Throw an exception if you want to.
        }
    }
}

如果\uuucxa\u thread\u atexit对您可用,您可能可以使用一些thread\u local对象来完成同样的事情,该对象在其析构函数中调用DetachCurrentThread

 类似资料:
  • 我在android应用程序中使用Stripe。目前,客户机服务器用于创建客户和对卡收费。 现在我想在应用程序中保存条带返回的卡信息,并在自定义ui中显示支付方式中添加的卡的列表,以便更新、删除和选择默认卡。 我浏览了stripe的临时密钥文档,他们提供了显示卡细节的活动。但是我找不到如何在自定义ui中显示卡片列表。 有没有其他方法可以实现这一点,例如在本地保存卡信息? 条纹是否允许自定义卡片列表等

  • 我想要一份有团队成员的团队名单 (我正在用java编写示例代码,但解决方案不必使用java。) 像这样: 如果我想访问一个团队的所有成员,那将是非常直接的<但是如果我想看看一个成员是哪个团队的成员呢<我不想每次都对所有团队成员进行循环。 我可以在成员的班级里拯救每个成员的团队,但这不是有点愚蠢吗?

  • 我需要根据一些配置数据启动多个独立的周期性任务——总数事先不知道。例如,我想检查具有不同间隔和不同参数的不同目录的内容,其中列表和参数是从配置中读取的。 在我看来,夸克斯调度器只能调度固定的、预先已知的方法。动态/编程调度的最佳方法是什么?<代码>vertx.set周期 是正确的方法还是我应该以某种方式访问Quartz?

  • 问题内容: 父类中有一个列表List。保存父项后,已添加或更改的子项应通过hibernate保存/更新。 我找到了很多解释,但是,我只是没有使它起作用。 Parent.class试试A Parent.class试试B 孩子被添加到新的父母。之后两者都保存 但是在冲洗时,出现以下错误: 有人看到我的错误吗? 非常感谢!! 问题答案: 我想如果您从第一条评论中回答这个问题,我们就会遇到这种情况: 你有

  • 所以,我试图编写一个gzip文件,实际上是从网络上编写的,但为了简化,我编写了一些非常基本的测试。 它运行得很好,我可以在Jupyter中看到它创建了测试。txt。目录列表中的gz文件。所以我点击它,希望看到一大堆垃圾字符表示一个二进制文件,就像你们在记事本中看到的一样<然而,我却得到了这个。。。 这让我想,哦,我的上帝,编码错误,我的编码有问题,我的保存,我能保存字节吗?我用的是正确的套路吗??

  • 有没有可能在lichess.org?保存党的分析让我描述一下这个场景:我的儿子使用lichess分析板来分析他的政党上面玩,它也允许“转换”手写的政党到PGN文件。 如果有可能在lichess.org保存特定的分析,拥有一个他曾经玩过的所有派对的云“库”,并且可以轻松地与他的象棋教练分享派对,那将是非常有帮助的。 也许这个特性已经在lichess的其他部分中实现了,但我没有找到它