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" |
| .... |
| } |
Wir verwenden die Methode in unserem Code:
| suspend fun <T> withContext( |
| context: CoroutineContext, |
| block: suspend CoroutineScope.() -> T |
| ) |
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") |
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() |
| } |
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() |
| } |
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" |
| ... |
| } |
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>>> |
| |
| } |
Beschreiben Sie das zurückgegebene Post-Modell:
| data class Post(val id: Int, val title: String) |
Unser BaseRepository:
| abstract class BaseRepository<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
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 |
| |
| } |
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() |
| } |
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) |
| } |
| } |
| } |
| } |
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()) |
| } |
| } |
Fazit
Ich habe Coroutinen im Produkt verwendet und der Code erwies sich als sauberer und lesbarer.
UPD:
Beschreibung der Ausnahmebehandlung nachrüsten siehe Kommentar