AsyncTask vs. RX - In einem kleinen Anwendungsfall

Kürzlich arbeitete ich an einer Aufgabe, bei der ich 12 Netzwerkanforderungen nacheinander synchronisieren musste. RESTful JSON-API-Anforderungen, eine nach der anderen.

Ich habe speziell daran gearbeitet, Optionen von einer Kamera anzufordern, die eine lokale API hostet, mit der Ihr Android-Gerät über WLAN eine Verbindung herstellen kann. Die API würde zurückgeben, welche Optionen verfügbar waren und welche Werte für jede dieser Optionen ausgewählt werden konnten.

Eine flexible API, mit der Sie die Verfügbarkeit mehrerer Optionen gleichzeitig mit einer Anfrage abfragen können. Ich hatte 12 Optionen, die mich interessierten, Belichtungseinstellungen und Blende und solche Optionen.

Das einzige Problem war, dass die Kamera-API als Antwort 404 zurückgab, wenn keine Option verfügbar war. Und auch wenn ich mehrfach angefragt habe! Wenn also nur eine Option von den 12 fehlte, bekam man eine 404 und wusste nichts über die anderen 11. Nun, das ist nutzlos, ich musste jede Option einzeln anfordern.

Ich habe jede dieser Optionen ausgewählt und sie in eine RecyclerView eingefügt, damit der Benutzer ihre Einstellungen über einen Drehknopf auswählen kann.

Ich habe RX bereits in Apps verwendet, an denen ich gearbeitet habe, insbesondere in RXJava2. Aber ich hatte noch nicht die Gelegenheit gehabt, es in meinem alltäglichen Job als Mitarbeiter am Unternehmensschalter zu nutzen.

Das Einfügen von Bibliotheken in eine Enterprise-Codebasis kann eine größere Herausforderung darstellen als das Einfügen in Start-up- oder Freelance-Situationen. Es ist nicht so, dass die Bibliotheken nicht großartig sind oder Probleme verursachen. Es ist so, dass eine Menge Leute an den Entscheidungen beteiligt sind und Sie gut darin sein müssen, verschiedene Arten der Codierung zu verkaufen.

Ich bin vielleicht noch nicht der Beste im Verkauf von Ideen, aber ich versuche mich zu verbessern!

Nun, hier habe ich das perfekte Beispiel für eine Situation, in der RX die Ausführung dieser 12 Anforderungen für mich einfacher und wartbarer macht.

Wir haben normalerweise AsyncTasks für unsere Hintergrundarbeit verwendet, wie dies in dieser App schon seit langer Zeit der Fall ist. Legacy hat ein Leben lang, sobald Sie sich für eine Technologie entschieden haben, wird sie für eine Weile dieser App folgen. Ein weiterer Grund, warum diese Entscheidungen nicht leichtfertig getroffen werden.

Ich neige dazu, neue Dinge auszuprobieren und auf dem neuesten Stand zu bleiben.

Noch besser, als ich einen Vergleich und ein Beispiel für RX und AsyncTask durchführen konnte, war die Tatsache, dass eine von uns verwendete Bibliothek eines Drittanbieters von RXJava Version 1 abhängig war.

Tief und siehe da, es war in unserer Codebasis und wartete darauf, benutzt zu werden.

Also machten ich mich und meine Kollegen mit der Genehmigung daran, einen Benchmark zu erstellen, um den Unterschied für diese eine Aufgabe zwischen der Verwendung von RX und AsyncTask zu testen.

Es stellt sich heraus, dass das Timing absolut vernachlässigbar ist! Hoffentlich beseitigt dies alle Mythen, dass die Verwendung von AsyncTask für kleine Hintergrundaufgaben langsam ist. Das erfahre ich ziemlich regelmäßig aus verschiedenen Quellen. Ich frage mich, was ich finden würde, wenn ich größere Tests machen würde.

Ich habe ein kleines Sampleset gemacht. Ich habe meine Aktivität sechsmal mit beiden Lösungen ausgeführt und Folgendes erhalten:

RX:
11–17 08: 59: 00.086 12 Empfang Abgeschlossene Anforderungen für Optionen: 3863 ms
11–17 08: 59: 20.018 12 Empfangsbestätigung für Optionen abgeschlossen: 3816 ms
11–17 08: 59: 39.143 12 Empfang Abgeschlossene Anforderungen für Optionen: 3628 ms
11–17 08: 59: 57.367 12 Empfang Abgeschlossene Anforderungen für Optionen: 3561 ms
11–17 09: 00: 15.758 12 Empfangsbestätigung für Optionen abgeschlossen: 3713 ms
11–17 09: 00: 39.129 12 Empfangsbestätigung für Optionen abgeschlossen: 3612 ms

Durchschnittliche Laufzeit 3698.83ms für meine RX-Lösung.

ATAsync:
11–17 08: 54: 49.277 12 Anforderungen für Optionen abgeschlossen: 4085 ms
11–17 08: 55: 37.718 12 Anforderungen für Optionen abgeschlossen: 3980 ms
11–17 08: 55: 59.819 12 Anforderungen für Optionen abgeschlossen: 3925 ms
11–17 08: 56: 20.861 12 Anforderungen für Optionen abgeschlossen: 3736 ms
11–17 08: 56: 41.438 12 Anforderungen für Optionen abgeschlossen: 3549 ms
11–17 08: 57: 01.110 12 Anforderungen für Optionen abgeschlossen: 3833 ms

Durchschnittliche Laufzeit 3851.33ms für meine AsyncTask-Lösung.

Die Verwendung von RX macht meiner Meinung nach kaum einen Unterschied bei den Laufzeiten. Wirklich, was die Laufzeit ausmacht, ist die Operation innerhalb der Hintergrundaufgabe, die Sie zu berechnen versuchen.

Was RX Ihnen jedoch bietet, ist die Wartbarkeit. Ihr Code ist viel einfacher auf dem neuesten Stand zu halten, weniger fehleranfällig. Sie können Ihre Lösung in der gleichen Reihenfolge logisch ausschreiben, in der sie ausgeführt wird. Dies ist ein enormer Vorteil, wenn Sie den Code logisch durchsuchen, wenn Sie in der Kälte springen.

Während es immer noch in Ordnung ist, nur AsyncTasks zu verwenden und jeder das tun kann, was er normalerweise tut, geht die Einführung von RX über Hintergrundaufgaben hinaus. Sie erhalten eine Welt voller neuer Möglichkeiten und leistungsstarker Methoden, mit denen Sie Ihre Arbeitsabläufe und Vorgänge funktional optimieren können. Mit RX können Sie viele Dinge tun, die Sie mit AysncTasks nicht tun können.

Schauen Sie sich die zusätzliche Arbeit an, die ich machen muss, um meine AsyncTask-Version zum Laufen zu bringen. Ich habe den Code so verschleiert, dass nichts unternehmenssensibles angezeigt wird. Dies ist ein Schein meines eigentlichen Codes.

AsyncTask-Version:

öffentliche Klasse OptionsCameraRequester implementiert IOptionRepository {
    ATAsyncTask currentTask;
    boolean isCanceled;
    letzter HttpConnector-Anschluss;
    private long startTime;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = neuer HttpConnector (ipAddress);
    }
    public void cancel () {
        isCanceled = true;
        if (currentTask! = null) {
            currentTask.cancel (true);
            currentTask = null;
        }
    }
    public void getOptions (Callback-Rückruf) {
        if (isCanceled) return;
        startTime = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "Anforderungen für Optionen gestartet");
        Iterator  iterator =
            CameraOption.getAllPossibleOptions (). Iterator ();
        requestOption (Iterator, Rückruf);
    }
    void requestOption (letzter Iterator  -Iterator,
                       letzter Rückruf Rückruf) {
        if (! iterator.hasNext ()) {
            letzte lange Zeit = System.currentTimeMillis ();
            Log.i (MyLog.TAG, "Anforderungen für Optionen abgeschlossen:" +
                    (System.currentTimeMillis () - startTime) +
                    "Frau");
            Rückkehr;
        }
        letzte CameraOption-Option = iterator.next ();
        final AsyncTask  task =
                neue AsyncTask  () {
                    CameraOption doInBackground (V ..) {
                        JSONObject result =
                            connector.getOption (option.getName ());
                        if (result == null) {
                            return null;
                        } else {
                            // Arbeiten Sie mit JSONObject
                        }
                        Rückgabeoption;
                    }
                    void onPostExecute (CameraOption-Option) {
                        OptionsCameraRequester.this.currentTask =
                            Null;
                        if (option! = null) {
                            callback.onOptionAvailable (Option);
                        }
                        if (! isCancelled) {
                            requestOption (Iterator, Rückruf);
                        }
                    }
                };
        task.execute ();
        currentTask = task;
    }
}

RX-Version:

öffentliche Klasse OptionsCameraRequester implementiert IOptionRepository {
    letzter HttpConnector-Anschluss;
    Subscription getOptionsSubscription;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = neuer HttpConnector (ipAddress);
    }
    public void cancel () {
        if (getOptionsSubscription! = null) {
            getOptionsSubscription.unsubscribe ();
            getOptionsSubscription = null;
        }
    }
    // Ich benutze den Rückruf, um mich an dasselbe System zu halten
    // Schnittstelle und halten Sie den RX-Code nur dazu enthalten
    // Klasse.

    public void getOptions (letzter Callback-Callback) {
        letzte lange Zeit = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "Empfangsanforderungen für Optionen gestartet");
        getOptionsSubscription =
        Observable.from (CameraOption.getAllPossibleOptions ())
            // Fordere jede Option von der Kamera an
            .map (neue Funktion1  () {
                    
                öffentlicher CameraOption-Aufruf (CameraOption-Option) {
                    JSONObject object =
                        connector.getOption (option.getName ());
                    if (object == null) {
                        cameraOption.setAvailable (false);
                    } else {
                        // Arbeiten Sie mit der Option JSONObject to init
                    }
                    return cameraOption;
               }
            })
            // Nicht unterstützte Optionen herausfiltern
            .filter (new Func1  () {
                    
                öffentlicher boolescher Aufruf (CameraOption cameraOption) {
                    return cameraOption.isAvailable ();
                }
            })
            // Thread-Arbeit für erledigt und empfangen deklarieren
            .observeOn (AndroidSchedulers.mainThread ())
            .subscribeOn (Schedulers.newThread ())
            // Übergebe jede Option, sobald sie fertig ist
            .subscribe (neuer Abonnent  () {
         
                public void onCompleted () {
                   getOptionsSubscription = null;
                   Log.i (MyLog.TAG, "RX Requests finished:" +
                        (System.currentTimeMillis () - time) + "ms");
                }
                public void onError (Throwable e) {
                   MyLog.eLogErrorAndReport (MyLog.TAG, e);
                   callback.onError ();
                }
                public void onNext (CameraOption cameraOption) {
                    callback.onOptionAvailable (cameraOption);
                }
           });
    }
}

Während der RX-Code länger aussieht, besteht keine Abhängigkeit mehr von der Verwaltung eines Iterators. Ein rekursiver Funktionsaufruf, der Threads wechselt, wird nicht abgebrochen. Es gibt keinen Booleschen Wert, der feststellt, dass die Aufgaben abgebrochen wurden. Alles wird in der Reihenfolge geschrieben, in der es ausgeführt wird.