Je mehr ich die Berichte über Coroutinen in Kotlin las und sah, desto mehr bewunderte ich dieses Sprachwerkzeug. Ihre stabile Version wurde kürzlich in Kotlin 1.3 veröffentlicht, was bedeutet, dass es Zeit ist, den Tauchgang zu starten und die Coroutinen in Aktion zu testen, wobei mein vorhandener RxJava-Code als Beispiel dient. In diesem Beitrag konzentrieren wir uns darauf, wie vorhandene Netzwerkanforderungen übernommen und konvertiert werden, indem RxJava durch Coroutinen ersetzt wird.

Ehrlich gesagt, bevor ich Coroutinen probierte, dachte ich, dass sie sich sehr von denen unterscheiden, die sie vorher waren. Das Grundprinzip von Corutin umfasst jedoch dieselben Konzepte, die wir in reaktiven RxJava-Flüssen gewohnt sind. Nehmen wir als Beispiel eine einfache RxJava-Konfiguration, um eine Netzwerkanforderung aus einer meiner Anwendungen zu erstellen:
- Definieren Sie die Netzwerkschnittstelle für die Nachrüstung mit dem Rx-Adapter ( retrofit2: adapter-rxjava2 ). Funktionen geben Objekte aus einem Rx-Framework zurück, z. B. Single oder Observable . (Hier werden Funktionen verwendet und keine Methoden, da davon ausgegangen wird, dass der alte Code auch in Kotlin geschrieben wurde. Nun, oder von Java über Android Studio konvertiert).
- Wir rufen eine bestimmte Funktion aus einer anderen Klasse auf (z. B. ein Repository oder eine Aktivität).
- Wir bestimmen für die Threads, auf welchem Scheduler sie ausgeführt werden, und geben das Ergebnis zurück (Methoden .subscribeOn () und .observeOn () ).
- Speichern Sie den Link zum Objekt zum Abbestellen (z. B. in CompositeObservable).
- Abonnieren Sie den Veranstaltungsstrom.
- Abbestellen des Streams abhängig von den Ereignissen des Aktivitätslebenszyklus.
Dies ist der grundlegende Algorithmus für die Arbeit mit Rx (ohne Berücksichtigung der Zuordnungsfunktionen und der Details anderer Datenmanipulationen). Was Corutin betrifft, ändert sich an dem Prinzip nicht viel. Das gleiche Konzept, nur die Terminologie ändert sich.
- Wir definieren die Netzwerkschnittstelle für das Retrofit mithilfe des Adapters für Coroutine . Funktionen geben verzögerte Objekte von der Corutin-API zurück.
- Wir rufen diese Funktionen aus einer anderen Klasse auf (z. B. einem Repository oder einer Aktivität). Der einzige Unterschied: Jede Funktion sollte als suspend markiert sein.
- Definieren Sie den Dispatcher, der für die Coroutine verwendet wird.
- Wir speichern den Link zum Job- Objekt zum Abbestellen.
- Führen Sie Coroutine auf jede mögliche Weise aus.
- Wir stornieren Coroutinen abhängig von den Ereignissen des Aktivitätslebenszyklus.
Wie Sie aus den obigen Sequenzen sehen können, ist der Ausführungsprozess von Rx und Corutin sehr ähnlich. Wenn wir die Implementierungsdetails nicht berücksichtigen, bedeutet dies, dass wir unseren Ansatz beibehalten können - wir ersetzen nur einige Dinge, um unsere Implementierung koroutinenfreundlich zu gestalten.

Der erste Schritt, den wir unternehmen müssen, besteht darin, Retrofit die Rückgabe von zurückgestellten Objekten zu ermöglichen. Aufgeschobene Objekte sind nicht blockierende Futures, die bei Bedarf rückgängig gemacht werden können. Diese Objekte sind im Wesentlichen ein Coroutine-Job, der den Wert für den entsprechenden Job enthält. Die Verwendung des verzögerten Typs ermöglicht es uns, dieselbe Idee wie Job zu mischen und zusätzlich zusätzliche Status wie Erfolg oder Misserfolg abzurufen. Dies macht ihn ideal für Netzwerkanforderungen.
Wenn Sie Retrofit mit RxJava verwenden, verwenden Sie wahrscheinlich die RxJava Call Adapter Factory. Glücklicherweise schrieb Jake Worton ihr Äquivalent für Coroutine .
Wir können diesen Aufrufadapter im Retrofit-Builder verwenden und dann unsere Retrofit-Schnittstelle auf dieselbe Weise wie bei RxJava implementieren:
private fun makeService(okHttpClient: OkHttpClient): MyService { val retrofit = Retrofit.Builder() .baseUrl("some_api") .client(okHttpClient) .addCallAdapterFactory(CoroutineCallAdapterFactory()) .build() return retrofit.create(MyService::class.java) }
Schauen wir uns nun die oben verwendete MyService-Oberfläche an. Wir müssen die zurückgegebenen Observable-Typen in der Retrofit-Schnittstelle durch Deferred ersetzen. Wenn es früher so war:
@GET("some_endpoint") fun getData(): Observable<List<MyData>>
Jetzt ersetzen wir es durch:
@GET("some_endpoint") fun getData(): Deferred<List<MyData>>
Jedes Mal, wenn wir getData () aufrufen, kehrt das zurückgestellte Objekt zu uns zurück - ein Analogon von Job für Netzwerkanforderungen. Bisher haben wir diese Funktion mit RxJava irgendwie aufgerufen:
override fun getData(): Observable<List<MyData>> { return myService.getData() .map { result -> result.map { myDataMapper.mapFromRemote(it) } } }
In diesem RxJava-Stream rufen wir unsere Dienstprogrammfunktion auf und wenden dann die Zuordnungsoperation von der RxJava-API mit der anschließenden Zuordnung der von der Anforderung zurückgegebenen Daten zu etwas an, das in der UI-Ebene verwendet wird. Dies wird sich ein wenig ändern, wenn wir eine Implementierung mit Coroutinen verwenden. Für den Anfang muss unsere Funktion ausgesetzt (zurückgestellt) werden, um eine verzögerte Operation innerhalb des Funktionskörpers durchzuführen. Und dafür muss auch die aufrufende Funktion verschoben werden. Eine verzögerte Funktion ist nicht blockierend und kann nach dem ersten Aufruf gesteuert werden. Sie können es starten, anhalten, fortsetzen oder abbrechen.
override suspend fun getData(): List<MyData> { ... }
Jetzt müssen wir unsere Utility-Funktion aufrufen. Auf den ersten Blick machen wir dasselbe, aber wir müssen uns daran erinnern, dass wir jetzt verzögert statt beobachtbar werden.
override suspend fun getData(): List<MyData> { val result = myService.getData() ... }
Aufgrund dieser Änderung können wir die Kartenoperationskette aus der RxJava-API nicht mehr verwenden. Und selbst zu diesem Zeitpunkt stehen uns keine Daten zur Verfügung - wir haben nur eine verzögerte Instanz. Jetzt müssen wir die Funktion await () verwenden, um auf das Ergebnis der Abfrage zu warten und dann den Code innerhalb der Funktion weiter auszuführen:
override suspend fun getData(): List<MyData> { val result = myService.getData().await() ... }
Zu diesem Zeitpunkt erhalten wir die abgeschlossene Anfrage und die daraus verfügbaren Daten zur Verwendung. Daher können wir jetzt Mapping-Operationen ausführen:
override suspend fun getData(): List<MyData> { val result = myService.getData().await() return result.map { myDataMapper.mapFromRemote(it) } }
Wir haben unsere Retrofit-Schnittstelle zusammen mit der aufrufenden Klasse genommen und Coroutinen verwendet. Jetzt möchten wir diesen Code aus unserer Aktivität oder aus Fragmenten aufrufen und die Daten verwenden, die wir vom Netzwerk erhalten haben.
In unserer Aktivität erstellen wir zunächst einen Link zu Job, in den wir unseren Coroutine-Vorgang zuweisen und ihn dann verwenden können, um beispielsweise das Abbrechen einer Anforderung während eines onDestroy () -Aufrufs zu steuern.
private var myJob: Job? = null override fun onDestroy() { myJob?.cancel() super.onDestroy() }
Jetzt können wir der Variablen myJob etwas zuweisen. Schauen wir uns unsere Anfrage mit Coroutinen an:
myJob = CoroutineScope(Dispatchers.IO).launch { val result = repo.getLeagues() withContext(Dispatchers.Main) { //do something with result } }
In diesem Beitrag möchte ich mich nicht mit Dispatchern befassen oder Operationen innerhalb von Coroutinen ausführen, da dies ein Thema für andere Beiträge ist. Kurz gesagt, was passiert hier:
- Erstellen Sie eine CoroutineScope-Instanz mit IO Dispatcher als Parameter. Dieser Dispatcher wird verwendet, um blockierende E / A-Vorgänge auszuführen, z. B. Netzwerkanforderungen.
- Wir starten unsere Coroutine mit der Startfunktion - diese Funktion startet eine neue Coroutine und gibt einen Link zu einer Variablen vom Typ Job zurück.
- Dann verwenden wir den Link zu unserem Repository, um Daten durch Ausführen einer Netzwerkanforderung zu empfangen.
- Am Ende verwenden wir den Haupt-Dispatcher, um die Arbeit am UI-Thread zu erledigen. Hier können wir den Benutzern die empfangenen Daten anzeigen.
Im nächsten Beitrag verspricht der Autor, etwas tiefer in die Details einzudringen, aber das aktuelle Material sollte ausreichen, um mit dem Studium von Coroutinen zu beginnen.
In diesem Beitrag haben wir die RxJava-Implementierung von Retrofit-Antworten durch verzögerte Objekte aus der Corutin-API ersetzt. Wir rufen diese Funktionen auf, um Daten aus dem Netzwerk zu empfangen, und zeigen sie dann in unserer Aktivität an. Ich hoffe, Sie haben gesehen, wie wenig Änderungen Sie vornehmen müssen, um mit Coroutinen zu beginnen, und die Einfachheit der API geschätzt, insbesondere beim Lesen und Schreiben von Code.
In den Kommentaren zum ursprünglichen Beitrag habe ich eine traditionelle Anfrage gefunden: Zeige den gesamten Code. Aus diesem Grund habe ich eine einfache Anwendung erstellt, die beim Start einen Zugfahrplan mit der Yandex. Schedules-API empfängt und in RecyclerView anzeigt. Link: https://github.com/AndreySBer/RetrofitCoroutinesExample
Ich möchte auch hinzufügen, dass Coroutinen ein schlechterer Ersatz für RxJava zu sein scheinen, da sie keinen äquivalenten Satz von Operationen zum Synchronisieren von Threads bieten. In diesem Zusammenhang lohnt es sich, die ReactiveX-Implementierung für Kotlin: RxKotlin zu betrachten .
Wenn Sie Android Jetpack verwenden, habe ich auch ein Beispiel mit Retrofit, Coroutinen, LiveData und MVVM gefunden: https://codinginfinite.com/kotlin-coroutine-call-adapter-retrofit/