Implementación del patrón "Observador-Suscriptor" utilizando devoluciones de llamada JNI en Android (NDK)

Implementación de Android (NDK) de devoluciones de llamada JNI, patrón de observador- suscriptor con NDK y devolución de llamada, propiedad exclusiva EventBus o Rx


... Lo entendí "no hay piezas reparables por el usuario en el interior". Quiero ver que hay ahí.
- Muñecas rusas de anidación hasta las profundidades. ¿En serio, Orozco? Juan no parecía lo que es una muñeca rusa de anidación.
"Es basura, profesor Gu". ¿Quién lo necesita para meterse con esto?
"El fin de los arcoiris" Vinge Vernor

Hay bastantes aplicaciones de Android que combinan código C ++ y Java. Java implementa la lógica de negocios, y C ++ hace todo el trabajo de computación, a menudo encontrado en el procesamiento de audio. El flujo de audio se procesa en algún lugar en el interior, y el freno con gas y embrague se muestra arriba, y datos para todo tipo de imágenes divertidas.
Bueno, desde ReactiveX, ya es familiar, para no cambiar su mano y trabajar con la mazmorra JNI de manera familiar, regularmente necesita implementar el patrón Observador en proyectos con NDK. Bueno, al mismo tiempo, la comprensión del código para arqueólogos aumentan aquellos que tienen mala suerte de "entender el código de otra persona".
Entonces, la mejor manera de aprender algo es hacerlo usted mismo.
Digamos que amamos y sabemos cómo escribir nuestras bicicletas. Y qué obtendremos como resultado:


  • algo así como una devolución de datos del código C ++ a los signatarios;
  • gestión del procesamiento en código nativo, es decir, es posible que no seamos obligados a llegar a acuerdos cuando no hay suscriptores y no hay nadie para enviarlos;
  • la transferencia de datos entre diferentes JVM puede ser necesaria;
  • y para no levantarse dos veces, al mismo tiempo enviar mensajes dentro de los flujos del proyecto.

El código de trabajo completo está disponible en GitHub . El artículo solo proporciona extractos de él.


Un poco de teoría e historia.


Recientemente estuve en la reunión de RX y me sorprendió la cantidad de preguntas sobre: ​​qué tan rápido es ReactiveX y cómo funciona en absoluto.
Para ReactiveX, solo puedo decir que para Java su velocidad depende en gran medida de qué tan razonablemente se use; si se usa correctamente, su velocidad es suficiente.
Nuestra bicicleta es mucho más liviana, pero si necesita, por ejemplo, una cola de mensajes (como fluida), debe escribirla usted mismo. Porque sabes que todos los problemas técnicos son solo tuyos.
Un poco de teoría: el patrón Observador- Suscriptor es un mecanismo que permite que un objeto reciba alertas sobre cambios en el estado de otros objetos y, por lo tanto, los observe. Se hace para reducir la conectividad y las dependencias entre los componentes del software, lo que les permite ser utilizados y probados de manera más eficiente. Un representante vívido en el que el concepto de lenguaje se basa en esto lo es todo: Smalltalk, todo basado en la idea de enviar mensajes. Objetivo afectado-C.


Implementación


Probemos con las mejores tradiciones de bricolaje, por así decirlo, "parpadea el LED". Si usa JNI, en el mundo NDK de Android, puede solicitar el método Java de forma asíncrona, en cualquier hilo. Esto es lo que usamos para construir nuestro "Observador".


El proyecto de demostración se basa en la nueva plantilla de proyecto de Android Studio.


Este es un método generado automáticamente. Él comentó:


// Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } 

Ahora para ti mismo. El primer paso es el método nsubscribeListener .


 private native void nsubscribeListener(JNIListener JNIListener); 

Permite que el código C ++ obtenga un enlace al código java para habilitar una devolución de llamada a un objeto que implementa la interfaz JNIListener.


 public interface JNIListener { void onAcceptMessage(String string); void onAcceptMessageVal(int messVal); } 

Los valores se transferirán a la implementación de sus métodos.


Para el almacenamiento en caché efectivo de enlaces a la máquina virtual, también guardamos el enlace al objeto. Tenemos un enlace global para ello.


 Java_ua_zt_mezon_myjnacallbacktest_MainActivity_nsubscribeListener(JNIEnv *env, jobject instance, jobject listener) { env->GetJavaVM(&jvm); //store jvm reference for later call store_env = env; jweak store_Wlistener = env->NewWeakGlobalRef(listener); 

Cuente y guarde enlaces a métodos inmediatamente. Esto significa que se requieren menos operaciones para completar la devolución de llamada.


 jclass clazz = env->GetObjectClass(store_Wlistener); jmethodID store_method = env->GetMethodID(clazz, "onAcceptMessage", "(Ljava/lang/String;)V"); jmethodID store_methodVAL = env->GetMethodID(clazz, "onAcceptMessageVal", "(I)V"); 

Los datos del suscriptor se almacenan como registros de clase ObserverChain .


 class ObserverChain { public: ObserverChain(jweak pJobject, jmethodID pID, jmethodID pJmethodID); jweak store_Wlistener=NULL; jmethodID store_method = NULL; jmethodID store_methodVAL = NULL; }; 

Guarde el suscriptor en la matriz dinámica store_Wlistener_vector.


 ObserverChain *tmpt = new ObserverChain(store_Wlistener, store_method, store_methodVAL); store_Wlistener_vector.push_back(tmpt); 

Ahora sobre cómo se transmitirán los mensajes del código Java.


Un mensaje enviado al método no Next se enviará a todos los signatarios.


 private native void nonNext(String message); 

Implementación


 Java_ua_zt_mezon_myjnacallbacktest_MainActivity_nonNext(JNIEnv *env, jobject instance, jstring message_) { txtCallback(env, message_); } 

Función txtCallback (env, message_); envía mensajes a todos los firmantes.


 void txtCallback(JNIEnv *env, const _jstring *message_) { if (!store_Wlistener_vector.empty()) { for (int i = 0; i < store_Wlistener_vector.size(); i++) { env->CallVoidMethod(store_Wlistener_vector[i]->store_Wlistener, store_Wlistener_vector[i]->store_method, message_); } } } 

Para reenviar mensajes de código C ++ o C, utilizamos la función test_string_callback_fom_c


 void test_string_callback_fom_c(char *val) 

Ella comprueba desde el principio si hay algún suscriptor.


 if (store_Wlistener_vector.empty()) return; 

Es fácil ver que la misma función txtCallback se usa para enviar mensajes:


 void test_string_callback_fom_c(char *val) { if (store_Wlistener_vector.empty()) return; __android_log_print(ANDROID_LOG_VERBOSE, "GetEnv:", " start Callback to JNL [%d] \n", val); JNIEnv *g_env; if (NULL == jvm) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", " No VM \n"); return; } // double check it's all ok JavaVMAttachArgs args; args.version = JNI_VERSION_1_6; // set 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 int getEnvStat = jvm->GetEnv((void **) &g_env, JNI_VERSION_1_6); if (getEnvStat == JNI_EDETACHED) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", " not attached\n"); if (jvm->AttachCurrentThread(&g_env, &args) != 0) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", " Failed to attach\n"); } } else if (getEnvStat == JNI_OK) { __android_log_print(ANDROID_LOG_VERBOSE, "GetEnv:", " JNI_OK\n"); } else if (getEnvStat == JNI_EVERSION) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", " version not supported\n"); } jstring message = g_env->NewStringUTF(val);// txtCallback(g_env, message); if (g_env->ExceptionCheck()) { g_env->ExceptionDescribe(); } if (getEnvStat == JNI_EDETACHED) { jvm->DetachCurrentThread(); } } 

En MainActivity creamos dos vistas de texto y una vista de edición.


 <TextView android:id="@+id/sample_text_from_Presenter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:padding="25dp" android:text="Hello World!from_Presenter" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintEnd_toStartOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/sample_text_from_act" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="Hello World from_ac!" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintStart_toStartOf="@+id/sample_text_from_Presenter" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/edit_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintStart_toStartOf="@+id/sample_text_from_act" app:layout_constraintTop_toTopOf="parent" /> 

En OnCreate, vinculamos Ver con variables y describimos que cuando se cambia el texto en EditText, se enviará un mensaje a los suscriptores.


 mEditText = findViewById(R.id.edit_text); mEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { nonNext(charSequence.toString()); } @Override public void afterTextChanged(Editable editable) { } }); tvPresenter = (TextView) findViewById(R.id.sample_text_from_Presenter); tvAct = (TextView) findViewById(R.id.sample_text_from_act); 

Comenzamos y registramos suscriptores:


 mPresenter = new MainActivityPresenterImpl(this); nsubscribeListener((MainActivityPresenterImpl) mPresenter); nlistener = new JNIListener() { @Override public void onAcceptMessage(String string) { printTextfrActObj(string); } @Override public void onAcceptMessageVal(int messVal) { } }; nsubscribeListener(nlistener); 

Se parece a esto:



El código de trabajo completo está disponible en GitHub https://github.com/NickZt/MyJNACallbackTest
Háganos saber en los comentarios qué describir con más detalle.

Source: https://habr.com/ru/post/es420389/


All Articles