android android-ndk java-native-interface jnienv

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.