Implémentation du modèle «Observer-Subscriber» à l'aide des rappels JNI dans Android (NDK)

Implémentation Android (NDK) des rappels JNI, modèle Observer- Subscriber avec NDK et rappel, EventBus ou Rx auto-écrit


... Je l'ai compris "il n'y a aucune pièce réparable par l'utilisateur à l'intérieur". Je veux voir ce qu'il y a.
- Poupées russes d'emboîtement jusqu'aux profondeurs. Vraiment, Orozco? Juan n'a pas regardé ce qu'est une poupée russe de nidification.
"Ce sont des ordures, professeur Gu." Qui en a besoin - pour jouer avec ça?
"La fin des arcs-en-ciel" Vinge Vernor

Il existe de nombreuses applications Android qui combinent le code C ++ et Java. Java implémente la logique métier, et C ++ fait tout le travail de l'informatique, souvent trouvé dans le traitement audio. Le flux audio est traité quelque part à l'intérieur, et le frein avec gaz et embrayage est affiché à l'étage, et les données pour toutes sortes d'images drôles.
Eh bien, depuis ReactiveX, c'est déjà familier, afin de ne pas changer de main, et de travailler avec le donjon JNI de manière familière, vous devez régulièrement implémenter le modèle Observer dans les projets avec NDK. Eh bien, en même temps, la compréhensibilité du code pour archéologues ceux qui n'ont pas de chance de «comprendre le code de quelqu'un d'autre» augmentent.
Donc, la meilleure façon d'apprendre quelque chose est de le faire vous-même.
Disons que nous aimons et savons comment écrire nos vélos. Et qu'obtiendrons-nous en conséquence:


  • quelque chose comme une publication du code C ++ aux signataires;
  • gestion du traitement en code natif, c'est-à-dire que nous ne pouvons pas être contraints à des règlements lorsqu'il n'y a pas d'abonnés et qu'il n'y a personne pour les envoyer;
  • le transfert de données entre différentes JVM peut être nécessaire;
  • et pour ne pas se lever deux fois, en même temps envoyer des messages à l'intérieur des flux du projet.

Le code de travail complet est disponible sur GitHub . L'article n'en fournit que des extraits.


Un peu de théorie et d'histoire


Récemment, j'étais à la réunion RX et j'ai été étonné du nombre de questions sur: à quelle vitesse est ReactiveX et comment cela fonctionne-t-il?
Pour ReactiveX, je peux seulement dire que pour Java sa vitesse dépend beaucoup de la façon dont il est raisonnablement utilisé; s'il est utilisé correctement, sa vitesse est suffisante.
Notre vélo est beaucoup plus léger, mais si vous avez besoin, par exemple, d'une file d'attente de messages (comme fluide), alors vous devez l'écrire vous-même. Parce que vous savez que tous les problèmes ne sont que les vôtres.
Un peu de théorie: le modèle Observer- Subscriber est un mécanisme qui permet à un objet de recevoir des alertes sur les changements d'état d'autres objets et donc de les observer. Cela permet de réduire la connectivité et les dépendances entre les composants logiciels, ce qui permet de les utiliser et de les tester plus efficacement. Un représentant vivant dans lequel le concept de langage est construit sur tout cela - Smalltalk, tous basés sur l'idée d'envoyer des messages. Objectif affecté-C.


Implémentation


Essayons dans les meilleures traditions du bricolage, pour ainsi dire, «clignoter la LED». Si vous utilisez JNI, dans le monde NDK Android, vous pouvez demander la méthode Java de manière asynchrone, dans n'importe quel thread. C'est ce que nous utilisons pour construire notre «Observateur».


Le projet de démonstration est basé sur le nouveau modèle de projet d'Android Studio.


Il s'agit d'une méthode générée automatiquement. Il a commenté:


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

Maintenant pour toi. La première étape est la méthode nsubscribeListener .


 private native void nsubscribeListener(JNIListener JNIListener); 

Il permet au code C ++ d'obtenir un lien vers le code java pour activer un rappel vers un objet qui implémente l'interface JNIListener.


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

Des valeurs seront transférées à la mise en œuvre de ses méthodes.


Pour une mise en cache efficace des liens vers la machine virtuelle, nous enregistrons également le lien vers l'objet. Nous obtenons un lien global pour cela.


 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); 

Comptez et enregistrez immédiatement les liens vers les méthodes. Cela signifie que moins d'opérations sont nécessaires pour terminer le rappel.


 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"); 

Les données d'abonné sont stockées sous forme d'enregistrements de classe ObserverChain .


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

Enregistrez l'abonné dans le tableau dynamique store_Wlistener_vector.


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

Maintenant, comment les messages du code Java seront transmis.


Un message envoyé à la méthode nonNext sera envoyé à tous les signataires.


 private native void nonNext(String message); 

Réalisation:


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

Fonction txtCallback (env, message_); envoie des messages à tous les signataires.


 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_); } } } 

Pour transférer des messages à partir du code C ++ ou C, nous utilisons la fonction test_string_callback_fom_c


 void test_string_callback_fom_c(char *val) 

Elle vérifie dès le début s'il y a des abonnés.


 if (store_Wlistener_vector.empty()) return; 

Il est facile de voir que la même fonction txtCallback est utilisée pour envoyer des messages:


 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(); } } 

Dans MainActivity, nous créons deux vues de texte et une vue d'édition.


 <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" /> 

Dans OnCreate, nous lions View avec des variables et décrivons que lorsque le texte est modifié dans EditText, un message sera envoyé aux abonnés.


 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); 

Nous commençons et enregistrons les abonnés:


 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); 

Cela ressemble à ceci:



Le code de travail complet est disponible sur GitHub https://github.com/NickZt/MyJNACallbackTest
Faites-nous savoir dans les commentaires ce qu'il faut décrire plus en détail.

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


All Articles