Implementieren des "Observer-Subscriber" -Musters mit JNI-Callbacks unter Android (NDK)

Published on August 16, 2018

Implementieren des "Observer-Subscriber" -Musters mit JNI-Callbacks unter Android (NDK)

  • Tutorial

Implementierung in Android (NDK) JNI-Callbacks, " Observer- Subscriber" -Muster mit NDK und Callback, selbstgeschriebener EventBus oder Rx


... "Ich habe keine vom Benutzer zu wartenden Teile." Ich möchte sehen, was da ist.
- Russische Matroschka bis in die Tiefe. Stimmt, Orozco? Juan fing nicht an zu schauen, was eine russische Puppe ist.
- Ja, es ist Müll, Professor Gu. Wer braucht es?
"Das Ende der Regenbogen" von Vinge Vernor

Es gibt einige Android-Anwendungen, die C ++ - und Java-Code kombinieren. Java implementiert Geschäftslogik, und C ++ erledigt alle Berechnungsaufgaben, die häufig in der Audioverarbeitung zu finden sind. Der Audiostrom wird irgendwo im Inneren verarbeitet, die Bremse mit Gas und Kupplung und die Daten für alle lustigen Bilder werden angezeigt.
Nun, da ReactiveX bereits vertraut ist, also sozusagen keine Hand zu wechseln und auf bekannte Weise mit dem JNI-Dungeon zu arbeiten, ist es regelmäßig erforderlich, das „Observer“ -Muster in Projekten mit NDK zu implementieren. Nun, gleichzeitig klarer Code fürArchäologenDiejenigen, die kein Glück haben, "den Code eines anderen zu verstehen", nehmen zu.
Der beste Weg, etwas zu lernen, ist also, es selbst zu tun.
Angenommen, wir lieben und wissen, wie man unsere Fahrräder schreibt. Und was wir als Ergebnis bekommen:


  • So etwas wie ein Postback vom C ++ - Code zum Abonnieren;
  • Management der Verarbeitung im nativen Code, dh wir können uns nicht auf die Berechnungen einlassen, wenn keine Abonnenten vorhanden sind und niemand sie senden kann;
  • Möglicherweise müssen Sie Daten zwischen verschiedenen JVMs übertragen.
  • und um nicht zweimal aufzustehen, gleichzeitig Nachrichten innerhalb der Threads des Projekts zu senden.

Vollständiger Arbeitscode ist auf GitHub verfügbar . Der Artikel enthält nur Auszüge daraus.


Ein bisschen Theorie und Geschichte


Vor kurzem war ich auf der RX mitap und ich war erstaunt über die Anzahl der Fragen: Wie schnell ReactiveX ist und wie es im Allgemeinen funktioniert.
Für ReactiveX kann ich nur sagen, dass die Geschwindigkeit für Java sehr stark davon abhängt, wie sinnvoll es verwendet wird, bei richtiger Verwendung ist die Geschwindigkeit durchaus ausreichend.
Unser Fahrrad ist viel leichter, aber wenn Sie zum Beispiel eine Nachrichtenwarteschlange benötigen (als fließfähig), müssen Sie es selbst schreiben. Dafür wissen Sie, dass alle Störungen nur Ihnen gehören.
Ein bisschen Theorie: das Beobachtermuster"Subscriber" ist ein Mechanismus, der es einem Objekt ermöglicht, Benachrichtigungen über Änderungen des Status anderer Objekte zu erhalten und diese zu überwachen. Es dient dazu, die Konnektivität und Abhängigkeiten zwischen Softwarekomponenten zu reduzieren, sodass sie effizienter verwendet und getestet werden können Smalltalk basiert auf der Idee, Nachrichten zu senden. Beeinflusst von Objective-C.


Umsetzung


Versuchen wir es in den besten Traditionen des DIY, sozusagen mit einer LED zu blinken. Wenn Sie JNI verwenden, können Sie in der Android NDK-Welt die Java-Methode asynchron in einem beliebigen Stream abfragen. So bauen wir unseren "Observer".


Das Demoprojekt basiert auf der Vorlage eines neuen Projekts aus Android Studio.


Dies ist eine automatisch generierte Methode. Er kommentierte:


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

Und jetzt zu dir selbst Der erste Schritt ist die Methode nsubscribeListener.


private native void nsubscribeListener(JNIListener JNIListener);

Es ermöglicht C ++ - Code, eine Verknüpfung zu Java-Code zu erhalten, um einen Rückruf zu einem Objekt aufzunehmen, das die Schnittstelle implementiert JNIListener.


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

Bei der Implementierung werden seine Methoden und Werte übertragen.


Für ein effektives Caching von Links zur virtuellen Maschine speichern wir auch die Verknüpfung zum Objekt. Wir bekommen dafür eine globale Verbindung.


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

Berechnen und speichern Sie sofort Verweise auf Methoden. Dies bedeutet, dass weniger Operationen erforderlich sind, um einen Rückruf durchzuführen.


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

Teilnehmerdaten werden als Klasseneinträge gespeichert ObserverChain.


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

Speichern Sie den Abonnenten im dynamischen Array store_Wlistener_vector.


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

Nun, wie Nachrichten aus einem Java-Code übertragen werden.


Eine an die nonNext-Methode gesendete Nachricht wird an alle Abonnenten gesendet.


private native void nonNext(String message);

Umsetzung:


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

Funktion txtCallback (env, message_); sendet Nachrichten an alle Abonnenten.


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

Verwenden Sie zum Weiterleiten von Nachrichten aus C ++ - oder C-Code die Funktion test_string_callback_fom_c


void test_string_callback_fom_c(char *val)

Es prüft von Anfang an, ob überhaupt Abonnenten vorhanden sind.


if (store_Wlistener_vector.empty())
    return;

Es ist leicht zu erkennen, dass dieselbe txtCallback-Funktion zum Senden von Nachrichten verwendet wird:


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

Erstellen Sie in der MainActivity zwei Textansicht und eine EditView.


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

In OnCreate verknüpfen wir die Ansicht mit Variablen und beschreiben, dass eine Nachricht an den Abonnenten gesendet wird, wenn sich der Text in EditText ändert.


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

Wir bekommen und registrieren Abonnenten:


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

Es sieht so aus:



Der vollständige Arbeitscode ist auf GitHub https://github.com/NickZt/MyJNACallbackTest verfügbar. Informieren
Sie sich in den Kommentaren, was Sie genauer beschreiben möchten.