
Saya memutuskan untuk menulis tentang beberapa hal yang, menurut pendapat saya, adalah dan tidak boleh dihindari ketika menggunakan coroutine Kotlin.
Bungkus panggilan tidak sinkron di coroutineScope atau gunakan SupervisorJob untuk menangani pengecualian
Jika pengecualian dapat terjadi di blok async
, jangan bergantung pada try/catch
.
val job: Job = Job() val scope = CoroutineScope(Dispatchers.Default + job)
Pada contoh di atas, fungsi doWork
memulai coroutine baru (1), yang bisa melempar pengecualian yang tidak tertangani. Jika Anda mencoba untuk membungkus doWork
dengan doWork
try/catch
(2), aplikasi akan tetap macet.
Ini karena kegagalan komponen anak dalam pekerjaan menyebabkan kegagalan langsung orang tuanya.
Salah satu cara untuk menghindari kesalahan adalah menggunakan SupervisorJob
(1).
Kegagalan atau pembatalan eksekusi komponen anak tidak akan menyebabkan kegagalan induk dan tidak akan mempengaruhi komponen lainnya.
val job = SupervisorJob()
Catatan : ini hanya akan berfungsi jika Anda secara eksplisit memulai panggilan korintin asinkron Anda dengan SupervisorJob
. Dengan demikian, kode di bawah ini masih akan crash aplikasi Anda, karena async
berjalan sebagai bagian dari induk coroutine (1).
val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job) fun loadData() = scope.launch { try { async {
Cara lain untuk menghindari crash, yang lebih disukai, adalah dengan membungkus async
di coroutineScope
(1). Sekarang, ketika pengecualian terjadi di dalam async
, itu membatalkan semua coroutine lain yang dibuat di area ini, tanpa menyentuh area eksternal. (2)
val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job)
Selain itu, Anda dapat menangani pengecualian di dalam blok async
.
Gunakan pengelola utama untuk coroutine root
Jika Anda perlu melakukan pekerjaan latar belakang dan memperbarui antarmuka pengguna di dalam root coroutine Anda, mulai gunakan dispatcher utama.
val scope = CoroutineScope(Dispatchers.Default)
Pada contoh di atas, kita mulai root coroutine menggunakan dispatcher CoroutineScope
di CoroutineScope
(1). Dengan pendekatan ini, setiap kali kita perlu memperbarui antarmuka pengguna, kita harus mengubah konteks (2).
Dalam kebanyakan kasus, lebih disukai untuk membuat CoroutineScope
segera dengan dispatcher utama, yang akan mengarah pada penyederhanaan kode dan pengalihan konteks yang kurang eksplisit.
val scope = CoroutineScope(Dispatchers.Main) fun login() = scope.launch { view.showLoading() withContext(Dispatcher.IO) { networkClient.login(...) } view.hideLoading() }
Hindari menggunakan async / menunggu yang tidak perlu
Jika Anda menggunakan fungsi async
dan segera menelepon await
, maka Anda harus berhenti melakukan ini.
launch { val data = async(Dispatchers.Default) { }.await() }
Jika Anda ingin mengganti konteks coroutine dan segera menangguhkan coroutine induk, maka withContext
adalah cara yang paling disukai untuk ini.
launch { val data = withContext(Dispatchers.Default) { } }
Dari sudut pandang kinerja, ini bukan masalah besar (bahkan mengingat bahwa async
membuat coroutine baru untuk melakukan pekerjaan itu), tetapi secara async
menyiratkan bahwa Anda ingin menjalankan beberapa coroutine di latar belakang dan hanya kemudian menunggu mereka.
Hindari pembatalan pekerjaan
Jika Anda perlu membatalkan coroutine, jangan membatalkan pekerjaan.
class WorkManager { val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job) fun doWork1() { scope.launch { } } fun doWork2() { scope.launch { } } fun cancelAllWork() { job.cancel() } } fun main() { val workManager = WorkManager() workManager.doWork1() workManager.doWork2() workManager.cancelAllWork() workManager.doWork1()
Masalah dengan kode di atas adalah bahwa ketika kami membatalkan pekerjaan, kami menempatkannya dalam status selesai . Coroutines diluncurkan sebagai bagian dari pekerjaan yang selesai tidak akan dieksekusi (1).
Jika Anda ingin membatalkan semua coroutine di area tertentu, Anda dapat menggunakan fungsi cancelChildren
. Selain itu, praktik yang baik untuk memberikan kemampuan membatalkan pekerjaan individu (2).
class WorkManager { val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job) fun doWork1(): Job = scope.launch { }
Hindari menulis fungsi jeda menggunakan dispatcher implisit
Jangan menulis fungsi suspend
, yang pelaksanaannya akan tergantung pada manajer coroutine tertentu.
suspend fun login(): Result { view.showLoading() val result = withContext(Dispatcher.IO) { someBlockingCall() } view.hideLoading() return result }
Pada contoh di atas, fungsi login adalah fungsi suspensi dan akan gagal jika Anda memulainya dari coroutine yang tidak akan digunakan oleh operator utama.
launch(Dispatcher.Main) {
CalledFromWrongThreadException: hanya utas sumber yang membuat hierarki komponen Tampilan yang dapat mengaksesnya.
Buat fungsi suspensi Anda sehingga dapat dieksekusi dari manajer coroutine apa pun.
suspend fun login(): Result = withContext(Dispatcher.Main) { view.showLoading() val result = withContext(Dispatcher.IO) { someBlockingCall() } view.hideLoading() return result }
Sekarang kita dapat memanggil fungsi login kita dari dispatcher mana pun.
launch(Dispatcher.Main) {
Hindari menggunakan ruang lingkup global
Jika Anda menggunakan GlobalScope
di mana-mana di aplikasi Android Anda, Anda harus berhenti melakukan ini.
GlobalScope.launch {
Ruang lingkup global digunakan untuk meluncurkan coroutine tingkat atas yang berjalan sepanjang masa aplikasi dan tidak dibatalkan sebelumnya.
Kode aplikasi biasanya harus menggunakan CoroutineScope yang ditentukan aplikasi, jadi tidak disarankan menggunakan async atau meluncurkan di GlobalScope .
Di Android, coroutine dapat dengan mudah dibatasi pada siklus hidup suatu Aktivitas, Fragmen, Tampilan, atau ViewModel.
class MainActivity : AppCompatActivity(), CoroutineScope { private val job = SupervisorJob() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onDestroy() { super.onDestroy() coroutineContext.cancelChildren() } fun loadData() = launch {