保存 JNIEnv* 的最佳方法是什么 [英] What is the best way to save JNIEnv*

查看:33
本文介绍了保存 JNIEnv* 的最佳方法是什么的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an Android project with JNI. In the CPP file which implements a listener class, there is a callback x() . When x() function is called, I want to call another function in a java class. However, in order to invoke that java function, I need to access JNIEnv*.

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

I know that in the same cpp file of the callback, there is a function:

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

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

Should I save in the cpp file JNIEnv* as member variable when init(..) is called? and use it later when the callback happens?

抱歉,我是 JNI 的初学者.

Sorry but I am a beginner in JNI.

推荐答案

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

Caching a JNIEnv* is not a particularly good idea, since you can't use the same JNIEnv* across multiple threads, and might not even be able to use it for multiple native calls on the same thread (see http://android-developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html)

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

Writing a function that gets the JNIEnv* and attaches the current thread to the VM if necessary isn't too difficult:

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 风格:

You could wrap this in a class that attaches upon construction and detaches upon destruction, RAII-style:

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 分离,但您仍然需要确保在线程销毁时分离线程.


Sometimes you might have a long-ish running native thread that will need a JNIEnv* on multiple occasions. In such situations you may want to avoid constantly attaching and detaching the thread to/from the JVM, but you still need to make sure that you detach the thread upon thread destruction.

您可以通过只附加线程一次然后保持附加,并使用 pthread_key_createpthread_setspecific 设置线程销毁回调来完成此操作调用 DetachCurrentThread.

You can accomplish this by attaching the thread only once and then leaving it attached, and by setting up a thread destruction callback using pthread_key_create and pthread_setspecific that will take care of calling 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.
        }
    }
}

如果 __cxa_thread_atexit 对您可用,您也许可以使用一些在其析构函数中调用 DetachCurrentThreadthread_local 对象来完成同样的事情.

If __cxa_thread_atexit is available to you, you might be able to accomplish the same thing with some thread_local object that calls DetachCurrentThread in its destructor.

这篇关于保存 JNIEnv* 的最佳方法是什么的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆