Erfahrung mit Coroutinen und Retrofit2

Was ist das?


Wer die Dokumentation noch nicht gelesen hat - ich empfehle Ihnen dringend, sich vertraut zu machen.


Was Jetbrain schreibt:


Coroutinen vereinfachen die asynchrone Programmierung und lassen alle Komplikationen in den Bibliotheken. Die Logik des Programms kann nacheinander in Coroutinen ausgedrückt werden, und die Basisbibliothek implementiert sie für uns asynchron. Die Bibliothek kann die entsprechenden Teile des Benutzercodes in Rückrufe einbinden, die die entsprechenden Ereignisse abonnieren, und die Ausführung an verschiedene Threads (oder sogar an verschiedene Computer!) Versenden. Der Code bleibt so einfach, als würde er streng sequentiell ausgeführt.

In einfachen Worten ist dies eine Bibliothek für die synchrone / asynchrone Codeausführung.


Warum?


Weil RxJava nicht mehr in Mode ist (nur ein Scherz).


Erstens wollte ich etwas Neues ausprobieren, und zweitens stieß ich auf einen Artikel - einen Vergleich der Geschwindigkeit von Corutin und anderer Methoden.


Beispiel


Beispielsweise müssen Sie eine Operation im Hintergrund ausführen.


Fügen Sie zunächst unsere build.gradle-Abhängigkeit von corutin hinzu:


dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
....
}
view raw build.gradle hosted with ❤ by GitHub

Wir verwenden die Methode in unserem Code:


suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
)
view raw gistfile1.txt hosted with ❤ by GitHub

Wo im Kontext - wir geben den Thread-Pool an, den wir benötigen - in einfachen Fällen sind dies IO, Main und Default

E / A - für einfache Operationen mit der API, Operationen mit der Datenbank, gemeinsame Einstellungen usw.
Haupt-UI-Thread, von dem aus wir auf die Ansicht zugreifen können
Standard - für schwere Operationen mit hoher CPU-Auslastung
(Mehr in diesem Artikel )


Block - das Lambda, das wir ausführen möchten


var result = 1.0
withContext(IO) {
for (i in 1..1000) {
result += i * i
}
}
Log.d("coroutines example", "result = $result")
view raw Example.kt hosted with ❤ by GitHub

Im Prinzip ist das alles, wir erhalten das Ergebnis der Summe der Quadrate von 1 bis 1000 und blockieren gleichzeitig nicht den Haupt-Thread, was bedeutet, dass keine ANR vorliegt


Wenn unsere Coroutine jedoch 20 Sekunden lang ausgeführt wird und wir während dieser Zeit 2 Umdrehungen des Geräts gemacht haben, haben wir 3 gleichzeitig laufende Blöcke. Ups


Und wenn wir einen Link zur Aktivität an den Block übergeben haben - ein Leck und die mangelnde Fähigkeit, Operationen mit Blick auf alte Blöcke auszuführen. Zweimal hoppla.


Was tun?


Besser machen


private var viewModelJob = Job()
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun doWork() {
var result = 1.0
viewModelScope.launch {
withContext(IO) {
for (i in 1..1000) {
result += i * i
}
}
}
Log.d("coroutines example", " result = $result")
}
fun cancelJob() {
viewModelJob.cancel()
}
view raw Example.kt hosted with ❤ by GitHub

So konnten wir die Ausführung unseres Codes im Stream stoppen, beispielsweise wenn der Bildschirm gedreht wird.


CoroutineScope ermöglichte die Kombination des Bereichs aller verschachtelten Coroutinen und stoppte beim Aufrufen von job.cancel () deren Ausführung


Wenn Sie den Bereich nach dem Stoppen der Ausführung wiederverwenden möchten, müssen Sie job.cancelChildren () anstelle von job.cancel () verwenden. Vielen Dank an Neikist für den Kommentar


Gleichzeitig haben wir immer noch die Möglichkeit, die Flüsse zu kontrollieren:


fun doWork() {
var result = 1.0
var result2 = 1.0
viewModelScope.launch {
withContext(IO) {
for (i in 1..1000) {
result += i * i
}
}
withContext(Default) {
for (i in 1..1000) {
result2 += i * i
}
}
}
Log.d("coroutines example", "running result = $result, result 2 = $result2")
}
fun cancelJob() {
viewModelJob.cancel()
}
view raw Example.kt hosted with ❤ by GitHub

Wir verbinden Retrofit2


Fügen Sie dem Hagel Abhängigkeiten hinzu:


dependencies {
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-moshi:$converterMoshiVersion"
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$retrofitCoroutinesVersion"
...
}
view raw build.gradle hosted with ❤ by GitHub

Wir verwenden den Stift https://my-json-server.typicode.com/typicode/demo/posts als Beispiel


Wir beschreiben die Nachrüstschnittstelle:


interface RetrofitPosts {
@GET("posts")
fun getPosts(): Deferred<Response<List<Post>>>
}
view raw RetrofitPosts.kt hosted with ❤ by GitHub

Beschreiben Sie das zurückgegebene Post-Modell:


data class Post(val id: Int, val title: String)
view raw Post.kt hosted with ❤ by GitHub

Unser BaseRepository:


abstract class BaseRepository<Params, Result> {
abstract suspend fun doWork(params: Params): Result
}
view raw BaseRepository.kt hosted with ❤ by GitHub

Implementierung von PostsRepository:


class PostsRepository :
BaseRepository<PostsRepository.Params, PostsRepository.Result>() {
override suspend fun doWork(params: Params): Result {
val retrofitPosts = Retrofit
.Builder()
.baseUrl("https://jsonplaceholder.typicode.com")
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
.create(RetrofitPosts::class.java)
val result = retrofitPosts
.getPosts()
.await()
return Result(result.body())
}
class Params
data class Result(val posts: List<Post>?)
}

Unser BaseUseCase:


abstract class BaseUseCase<Params, Result> {
abstract suspend fun doWork(params: Params): Result
}
view raw BaseUseCase.kt hosted with ❤ by GitHub

Implementierung von GetPostsListUseCase:


class GetListOfPostsUseCase
: BaseUseCase<GetListOfPostsUseCase.Params, GetListOfPostsUseCase.Result>() {
override suspend fun doWork(params: Params): Result {
return Result(
PostsRepository()
.doWork(PostsRepository.Params())
.response
.posts
)
}
class Params
class Result(val posts: List<Post>?)
}

Hier ist das Ergebnis:


fun doWork() {
val useCase = GetListOfPostsUseCase()
viewModelScope.launch {
withContext(Dispatchers.IO) {
val result = useCase.doWork(
GetListOfPostsUseCase.Params()
)
Log.d("coroutines example", "get list of posts = ${result.posts}")
}
}
}
fun cancelJob() {
viewModelJob.cancel()
}
view raw Example.kt hosted with ❤ by GitHub

Besser machen


Ich bin eine faule Kreatur und jedes Mal, wenn ich nicht das gesamte Codeblatt ziehen möchte, habe ich die erforderlichen Methoden in BaseViewModel erstellt:


abstract class BaseViewModel : ViewModel() {
private var viewModelJob = Job()
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private var isActive = true
// Do work in IO
fun <P> doWork(doOnAsyncBlock: suspend CoroutineScope.() -> P) {
doCoroutineWork(doOnAsyncBlock, viewModelScope, IO)
}
// Do work in Main
// doWorkInMainThread {...}
fun <P> doWorkInMainThread(doOnAsyncBlock: suspend CoroutineScope.() -> P) {
doCoroutineWork(doOnAsyncBlock, viewModelScope, Main)
}
// Do work in IO repeately
// doRepeatWork(1000) {...}
// then we need to stop it calling stopRepeatWork()
fun <P> doRepeatWork(delay: Long, doOnAsyncBlock: suspend CoroutineScope.() -> P) {
isActive = true
viewModelScope.launch {
while (this@BaseViewModel.isActive) {
withContext(IO) {
doOnAsyncBlock.invoke(this)
}
if (this@BaseViewModel.isActive) {
delay(delay)
}
}
}
}
fun stopRepeatWork() {
isActive = false
}
override fun onCleared() {
super.onCleared()
isActive = false
viewModelJob.cancel()
}
private inline fun <P> doCoroutineWork(
crossinline doOnAsyncBlock: suspend CoroutineScope.() -> P,
coroutineScope: CoroutineScope,
context: CoroutineContext
) {
coroutineScope.launch {
withContext(context) {
doOnAsyncBlock.invoke(this)
}
}
}
}
view raw BaseViewModel.kt hosted with ❤ by GitHub

Das Abrufen der Liste der Beiträge sieht nun folgendermaßen aus:


class PostViewModel : BaseViewModel() {
val lengthOfPostsList = MutableLiveData<String>()
fun getListOfPosts() {
doWork {
val result = GetListOfPostsUseCase()
.doWork(GetListOfPostsUseCase.Params())
Log.d("coroutines example", "get list of posts = ${result.posts}")
lengthOfPostsList.postValue(result.posts?.size.toString())
}
}
view raw PostViewModel.kt hosted with ❤ by GitHub

Fazit


Ich habe Coroutinen im Produkt verwendet und der Code erwies sich als sauberer und lesbarer.


UPD:
Beschreibung der Ausnahmebehandlung nachrüsten siehe Kommentar

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


All Articles