首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >拯救JNIEnv的最佳方法是什么*

拯救JNIEnv的最佳方法是什么*
EN

Stack Overflow用户
提问于 2015-05-04 08:46:41
回答 2查看 5.8K关注 0票数 9

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

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

代码语言:javascript
复制
static jboolean init (JNIEnv* env, jobject obj) {...}

当调用init(..)时,是否应该将cpp文件JNIEnv*保存为成员变量?当回调发生的时候再用它?

对不起,我是JNI的初学者。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-05-04 09:00:01

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

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

代码语言:javascript
复制
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;
}

你使用它的方式是:

代码语言:javascript
复制
JNIEnv *env;
bool did_attach = GetJniEnv(vm, &env);
// Use env...
// ...
if (did_attach) {
   vm->DetachCurrentThread();
}

您可以将其封装在一个类中,类在构造时附加,在破坏时分离,RAII样式:

代码语言:javascript
复制
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或从JVM中分离出来,但仍然需要确保在线程销毁时分离线程。

您可以通过只附加线程一次,然后将线程保持连接,以及使用pthread_key_createpthread_setspecific设置线程销毁回调来完成这一任务,这将负责调用DetachCurrentThread

代码语言:javascript
复制
/**
 * 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.
        }
    }
}

如果您可以使用__cxa_thread_atexit,那么您可能可以通过在析构函数中调用DetachCurrentThread的某个thread_local对象来完成同样的任务。

票数 28
EN

Stack Overflow用户

发布于 2020-01-27 16:16:42

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

Bellow是关于如何实现包装器方法的粗略示例,该方法正确地附加到线程上,并在线程退出时自动清除。

代码语言:javascript
复制
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;
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30026030

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档