Apa ini
Siapa yang belum membaca dokumentasi - Saya sangat menyarankan Anda untuk membiasakan diri.
Apa yang ditulis Jetbrain:
Coroutines menyederhanakan pemrograman asinkron, meninggalkan semua komplikasi di dalam perpustakaan. Logika program dapat diekspresikan secara berurutan dalam coroutine, dan pustaka dasar akan mengimplementasikannya secara asinkron untuk kita. Pustaka dapat membungkus bagian-bagian yang sesuai dari kode pengguna dalam panggilan balik yang berlangganan acara terkait, dan mengirimkan eksekusi ke utas yang berbeda (atau bahkan ke mesin yang berbeda!). Kode akan tetap sesederhana jika dijalankan secara berurutan.
Secara sederhana, ini adalah pustaka untuk eksekusi kode sinkron / asinkron.
Mengapa
Karena RxJava tidak lagi dalam mode (hanya bercanda).
Pertama, saya ingin mencoba sesuatu yang baru, dan kedua, saya menemukan artikel - perbandingan kecepatan corutin dan metode lainnya.
Contoh
Misalnya, Anda perlu melakukan operasi di latar belakang.
Untuk memulai - tambahkan dependensi build.gradle kami pada corutin:
| dependencies { |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1" |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" |
| .... |
| } |
Kami menggunakan metode dalam kode kami:
| suspend fun <T> withContext( |
| context: CoroutineContext, |
| block: suspend CoroutineScope.() -> T |
| ) |
Di mana dalam konteks - kami menentukan kumpulan utas yang kami butuhkan - dalam kasus sederhana, ini adalah IO, Utama dan Default
IO - untuk operasi sederhana dengan API, operasi dengan database, preferensi bersama, dll.
Main - UI thread, dari mana kita dapat mengakses tampilan
Default - untuk operasi berat dengan beban CPU yang tinggi
(Lebih banyak di artikel ini)
Block - lambda yang ingin kita eksekusi
| var result = 1.0 |
| withContext(IO) { |
| for (i in 1..1000) { |
| result += i * i |
| } |
| } |
| Log.d("coroutines example", "result = $result") |
Pada prinsipnya, itu saja, kami mendapatkan hasil dari jumlah kuadrat dari 1 hingga 1000 dan pada saat yang sama kami tidak memblokir utas utama, yang berarti tidak ada ANR
Namun, jika coroutine kami dijalankan selama 20 detik dan selama waktu ini kami telah membuat 2 putaran perangkat, maka kami akan memiliki 3 blok yang berjalan secara bersamaan. Ups
Dan jika kami melewati tautan ke aktivitas ke blok - kebocoran dan kurangnya kemampuan untuk melakukan operasi dengan tampilan di blok lama. Ups dua kali.
Jadi apa yang harus dilakukan?
Melakukan yang lebih baik
| 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() |
| } |
Dengan demikian, kami dapat menghentikan eksekusi kode kami di arus, misalnya, ketika layar diputar.
CoroutineScope memungkinkan untuk menggabungkan ruang lingkup semua coroutine bersarang dan, ketika memanggil job.cancel (), menghentikan eksekusi mereka
Jika Anda berencana untuk menggunakan kembali ruang lingkup setelah menghentikan eksekusi, Anda perlu menggunakan job.cancelChildren () alih-alih job.cancel () terima kasih Neikist untuk komentar
Pada saat yang sama, kami masih memiliki kesempatan untuk mengontrol arus:
| 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() |
| } |
Kami menghubungkan retrofit2
Tambahkan dependensi ke hujan es:
| |
| dependencies { |
| implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" |
| implementation "com.squareup.retrofit2:converter-moshi:$converterMoshiVersion" |
| implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$retrofitCoroutinesVersion" |
| ... |
| } |
Kami menggunakan pena https://my-json-server.typicode.com/typicode/demo/posts sebagai contoh
Kami menjelaskan antarmuka retrofit:
| interface RetrofitPosts { |
| |
| @GET("posts") |
| fun getPosts(): Deferred<Response<List<Post>>> |
| |
| } |
Jelaskan model Pos yang dikembalikan:
| data class Post(val id: Int, val title: String) |
BaseRepository kami:
| abstract class BaseRepository<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
Implementasi dari 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>?) |
| } |
BaseUseCase Kami:
| abstract class BaseUseCase<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
Implementasi 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>?) |
| } |
Inilah hasilnya:
| 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() |
| } |
Membuatnya Lebih Baik
Saya adalah makhluk yang malas dan setiap kali saya tidak ingin menyeret seluruh lembar kode, jadi saya membuat metode yang diperlukan di BaseViewModel:
| 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) |
| } |
| } |
| } |
| } |
Sekarang mendapatkan daftar Posting terlihat seperti ini:
| 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()) |
| } |
| } |
Kesimpulan
Saya menggunakan coroutine di prod dan kodenya benar-benar menjadi lebih bersih dan lebih mudah dibaca.
UPD:
Perkecualian perkuatan menangani deskripsi lihat komentar