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*.
我有一个JNI的Android项目。在实现侦听器类的CPP文件中,有一个回调x()。当调用x()函数时,我想调用java类中的另一个函数。但是,为了调用那个java函数,我需要访问JNIEnv *。
I know that in the same cpp file of the callback, there is a function:
我知道在回调的同一个cpp文件中,有一个函数:
static jboolean init (JNIEnv* env, jobject obj) {...}
Should I save in the cpp file JNIEnv* as member variable when init(..)
is called? and use it later when the callback happens?
在调用init(..)时,我应该将cpp文件JNIEnv *保存为成员变量吗?并在以后回调发生时使用它?
Sorry but I am a beginner in JNI.
对不起,我是JNI的初学者。
1 个解决方案
#1
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 *并不是一个特别好的主意,因为你不能在多个线程中使用相同的JNIEnv *,甚至可能无法在同一个线程上使用它进行多个本机调用(参见http:// android- developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html)
Writing a function that gets the JNIEnv*
and attaches the current thread to the VM if necessary isn't too difficult:
编写一个获取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;
}
The way you'd use it is:
你使用它的方式是:
JNIEnv *env;
bool did_attach = GetJniEnv(vm, &env);
// Use env...
// ...
if (did_attach) {
vm->DetachCurrentThread();
}
You could wrap this in a class that attaches upon construction and detaches upon destruction, RAII-style:
你可以将它包装在一个附着在构造上的类中,并在破坏时分离,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
Edit: 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.
编辑:有时你可能有一个长期运行的本机线程,需要多次JNIEnv *。在这种情况下,您可能希望避免不断地将线程连接到JVM或从JVM断开线程,但是仍然需要确保在线程销毁时分离线程。
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
.
您可以通过仅连接一次线程然后保持连接来完成此操作,并使用pthread_key_create和pthread_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.
}
}
}
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.
如果您可以使用__cxa_thread_atexit,则可以使用在其析构函数中调用DetachCurrentThread的某个thread_local对象来完成相同的操作。
#1
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 *并不是一个特别好的主意,因为你不能在多个线程中使用相同的JNIEnv *,甚至可能无法在同一个线程上使用它进行多个本机调用(参见http:// android- developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html)
Writing a function that gets the JNIEnv*
and attaches the current thread to the VM if necessary isn't too difficult:
编写一个获取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;
}
The way you'd use it is:
你使用它的方式是:
JNIEnv *env;
bool did_attach = GetJniEnv(vm, &env);
// Use env...
// ...
if (did_attach) {
vm->DetachCurrentThread();
}
You could wrap this in a class that attaches upon construction and detaches upon destruction, RAII-style:
你可以将它包装在一个附着在构造上的类中,并在破坏时分离,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
Edit: 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.
编辑:有时你可能有一个长期运行的本机线程,需要多次JNIEnv *。在这种情况下,您可能希望避免不断地将线程连接到JVM或从JVM断开线程,但是仍然需要确保在线程销毁时分离线程。
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
.
您可以通过仅连接一次线程然后保持连接来完成此操作,并使用pthread_key_create和pthread_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.
}
}
}
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.
如果您可以使用__cxa_thread_atexit,则可以使用在其析构函数中调用DetachCurrentThread的某个thread_local对象来完成相同的操作。