android - ¿Cuál es la mejor manera de salvar JNIEnv*
android-ndk java-native-interface (1)
Tengo un proyecto de Android con JNI. En el archivo CPP que implementa una clase de escucha, hay una devolución de llamada x (). Cuando se llama a la función x (), quiero llamar a otra función en una clase java. Sin embargo, para invocar esa función de Java, necesito acceder a JNIEnv *.
Sé que en el mismo archivo cpp de la devolución de llamada, hay una función:
static jboolean init (JNIEnv* env, jobject obj) {...}
¿Debo guardar en el archivo cpp JNIEnv * como variable miembro cuando se llama a
init(..)
?
y usarlo más tarde cuando ocurra la devolución de llamada?
Lo siento pero soy un principiante en JNI.
El almacenamiento en caché de un
JNIEnv*
no es una idea particularmente buena, ya que no puede usar el mismo
JNIEnv*
en varios subprocesos, y es posible que ni siquiera pueda usarlo para múltiples llamadas nativas en el mismo subproceso (consulte
http://android-developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html
)
Escribir una función que obtenga el
JNIEnv*
y
JNIEnv*
el hilo actual a la VM si es necesario no es demasiado difícil:
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;
}
La forma en que lo usarías es:
JNIEnv *env;
bool did_attach = GetJniEnv(vm, &env);
// Use env...
// ...
if (did_attach) {
vm->DetachCurrentThread();
}
Podría envolver esto en una clase que se adhiere en la construcción y se desprende en la destrucción, al estilo 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
Editar: a
veces puede tener un hilo nativo de larga ejecución que necesitará un
JNIEnv*
en múltiples ocasiones.
En tales situaciones, es posible que desee evitar conectar y desconectar constantemente el hilo hacia / desde la JVM, pero aún así debe asegurarse de desconectar el hilo tras la destrucción del hilo.
Puede lograr esto conectando el hilo solo una vez y luego dejándolo conectado, y configurando una devolución de llamada de destrucción de hilo usando
pthread_key_create
y
pthread_setspecific
que se encargará de llamar a
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.
}
}
}
Si
__cxa_thread_atexit
está disponible para usted, es posible que pueda lograr lo mismo con algún objeto
thread_local
que llame a
DetachCurrentThread
en su destructor.