Android Kotlin Coroutine-ning eng yaxshi amaliyotlari

Bu Android-da Kotlin Coroutines-dan foydalanish bo'yicha doimiy ravishda saqlanib turadigan eng yaxshi tajribalar to'plami. Iltimos, qo'shilishi kerak bo'lgan biron bir taklifingiz bo'lsa, iltimos, quyida sharhlang.

  1. Android Lifecycles bilan ishlash

RxJava bilan CompositeDisposables-ni ishlatganingiz singari, Kotlin Coroutines o'z vaqtida "Faoliyat va Fragmentlar" bilan Android Livecycles-ni xabardor qilish orqali bekor qilinishi kerak.

a) Android Viewmodels-dan foydalanish

Bu koutuinlarni sozlashning eng oson usuli, ular o'z vaqtida yopiladi, lekin u faqatgina Android ViewModel-da ishlaydi, koutinali ishlarni ishonchli ravishda bekor qilish mumkin:

xususiy val viewModelJob = Ish ()
xususiy val uiScope = CoroutineScope (DispetcherlarMain + viewModelJob)
onCleared () {kulgisini bekor qil
 super.onCleared ()
 uiScope.coroutineContext.cancelChildren ()
}

Izoh: ViewModels 2.1.0-alpha01-ga ko'ra, endi bu kerak emas. Sizga endi KoroutineScope dasturini ishga tushirish, ishga tushirish yoki ish qo'shish shart emas. Faqat "viewModelscope.launch {}" dan foydalaning. Shuni yodda tutingki, 2.x sizning ilovangiz AndroidX-da bo'lishi kerak, chunki ular buni ViewModels-ning 1.x versiyasiga zaxira qilishni rejalashtirishlariga ishonchim komil emas.

b) Hayotiy tsiklni kuzatuvchilardan foydalanish

Ushbu boshqa usul siz biron-bir qism yoki qismga (yoki Android hayot tsiklini tatbiq etadigan boshqa narsalarga) biriktiradigan doirani yaratadi:

/ **
 UI yo'q qilinganida, koreyalik kontekst avtomatik ravishda bekor qilinadi
 * /
sinf UiLifecycleScope: CoroutineScope, LifecycleObserver {

    private lateinit var ish: Ish
    val coroutineContextni bekor qiling: CoroutineContext
        get () = ish + dispetcherlar.Main

    @OnLifecycleEvent (Lifecycle.Event.ON_START)
    qiziqarli onCreate () {
        ish = ish ()
    }

    @On hayot davriEvent (Hayotiy tsikl.Event.ON_PAUSE)
    fun məhv () = job.cancel ()
}
... "Lib Faoliyat" yoki "Fragment" ni qo'llab-quvvatlash
uiScope = UiLifecycleScope ()
onCreate-ning kulgisini bekor qilmoq (saqlanganInstanceState: to'plam) {
  super.onCreate (saqlanganInstanceState)
  lifecycle.addObserver (uiccope)
}

c) GlobalScope

Agar siz GlobalScope-dan foydalansangiz, ilova umr bo'yi davom etadi. Siz buni fonda sinxronlash, repo yangilash va boshqalar uchun ishlatasiz (Faoliyat tsikliga bog'lanmagan).

d) Xizmatlar

Xizmatlar onDestroy-dagi ish joylarini bekor qilishi mumkin:

xususiy val serviceJob = Ish ()
shaxsiy val serviceScope = CoroutineScope (DispetcherlarMain + serviceJob)
onCleared () {kulgisini bekor qil
 super.onCleared ()
 serviceJob.cancel ()
}

2. Istisnolardan foydalanish

a) In async vs. ishga tushirish va ishga tushirishni bloklash

Shuni ta'kidlash kerakki, ishga tushirish {} blokidagi istisnolar dasturni istisnosiz ishlov beruvchisiz buzadi. Boshlash uchun parametr sifatida o'tish uchun har doim standart istisno ishlov beruvchisini o'rnating.

RunBlocking {} blokidagi istisno, agar siz urinib ko'rishni qo'shmasangiz, dastur buziladi. Agar siz runBlocking-dan foydalanayotgan bo'lsangiz, har doim sinov / tutish qo'shing. Ideal holda, faqat birlik sinovlari uchun runBlocking-dan foydalaning.

Async {} blokiga tashlangan istisno, blok kutilmaguncha tarqalmaydi yoki ishlamaydi, chunki bu haqiqatan ham Java tomonidan kechiktirilgan. Qo'ng'iroq funktsiyasi / usuli istisnolardan iborat bo'lishi kerak.

b) qo'lga olish uchun istisnolar

Agar siz istisnolarni chiqarib yuboradigan kodni ishlatish uchun async-dan foydalansangiz, istisnolarni to'g'ri ushlab turish uchun kodni coroutineScope-ga o'rashingiz kerak (misol uchun LouisC-ga rahmat):

urinib ko'ring {
    coroutineScope {
        val mayFailAsync1 = async {
            mayFail1 ()
        }
        val mayFailAsync2 = async {
            MayFail2 ()
        }
        useResult (mayFailAsync1.await (), mayFailAsync2.await ())
    }
} qo'lga olish (e: IOException) {
    // buni boshqarish
    otish MyIoException ("IOni bajarishda xato", e)
} qo'lga olish (e: AnotherException) {
    // buni ham bajaring
    otish MyOtherException ("Biror narsani bajarishda xato", e)
}

Istisnoni qo'lga kiritganingizda, uni boshqa istisnoga (RxJava uchun qilgan narsangizga o'xshash) o'rab oling, shunda staktrlash chizig'ini faqat krointin kodi bilan stacktrace ko'rish o'rniga o'z kodingizda olasiz.

c) ro'yxatga olishda istisnolar

Agar GlobalScope.launch yoki aktyordan foydalansangiz, har doim istisnolarni yozib qo'yadigan istisnosiz ishlov beruvchidan o'ting. Masalan

val errorHandler = CoroutineExceptionHandler {_, istisno ->
  // Crashlytics, logcat va boshqalarga kirish.
}
val job = GlobalScope.launch (errorHandler) {
...
}

Deyarli har doim, siz Android-da tizimli tuzilmalarni ishlatishingiz kerak va ishlov beruvchidan foydalanish kerak:

val errorHandler = CoroutineExceptionHandler {_, istisno ->
  // Crashlytics, logcat va boshqalarga kirish; qaramlik kiritilishi mumkin
}
val nazoratchisi = SupervisorJob () // bekor qilingan / Faoliyat davri
bilan (CoroutineScope (coroutineContext + rahbar)) {
  val narsa = ishga tushirish (errorHandler) {
    ...
  }
}

Agar siz asynkni ishlatsangiz va kutayotgan bo'lsangiz, yuqorida tavsiflanganidek, har doim sinab ko'ring / tuting, lekin kerak bo'lganda kiring.

d) Natija / Xato muhrlangan sinfni ko'rib chiqing

Istisnolarni tashlash o'rniga xatoga yo'l qo'yadigan natija muhrlangan sinfdan foydalanishni ko'rib chiqing:

muhrlangan sinf natijasi  {
  ma'lumot sinfi Muvaffaqiyat (val ma'lumotlar: T): Natija ()
  ma'lumotlar sinfidagi xato (val xatosi: E): Natija ()
}

e) Koroutin kontekstini nomlang

Asinb lambda deb e'lon qilinganda, siz ham shunday nomlashingiz mumkin:

asinx (CoroutineName ("MyCoroutine")))}}

Agar ishga tushirish uchun o'z ipingizni yaratayotgan bo'lsangiz, uni bajaruvchini yaratishda ham nomlashingiz mumkin:

yangiSingleThreadContext ("MyCoroutineThread")

3. Ijrochining hovuzlari va odatiy hovuz o'lchamlari

Coroutines haqiqatan ham cheklangan ip pulining o'lchamidagi kooperativ (kompilyator yordamida). Bu shuni anglatadiki, agar siz korrutiningizda biron bir narsani blokirovka qilsangiz (masalan, blokirovka qilish API-dan foydalansangiz), blokirovka qilish jarayoni tugamaguncha butun ipni bog'lab qo'yasiz. Agar kortej hosil qilsangiz yoki kechiktirmasangiz, korrutin to'xtatilmaydi, shuning uchun agar sizda uzoq ishlov berish loopi mavjud bo'lsa, koutinaning bekor qilingan-qilinmaganligini tekshirib ko'ring (qamrovda "sureActive ()" ni chaqiring), shunda siz bo'shashingiz mumkin ip; bu RxJava qanday ishlashiga o'xshaydi.

Kotlin koroutinalarida bir nechta dispetcherlar o'rnatilgan (RxJava-dagi rejalashtiruvchilarga teng). Asosiy dispetcher (agar ishga tushirish uchun hech narsa ko'rsatmasangiz) UI UI; faqat shu kontekstda UI elementlarini o'zgartirishingiz kerak. U erda bir nechta dispetcherlar bor. UI va fon iplari o'rtasida bitta ipga tushmasligi uchun u bog'lanishi mumkin; umuman olganda, birlik sinovlaridan tashqari foydalanish kerak emas. IOni boshqarish uchun dispetcher.IO mavjud (tez-tez to'xtab turadigan tarmoq qo'ng'iroqlari). Va nihoyat, asosiy fon oqimlari fondi bo'lgan Dispatchers.Default mavjud, ammo bu CPU soni bilan cheklangan.

Amalda, siz turli xil sinovlarni almashtirish uchun sinf konstruktori orqali o'tadigan oddiy dispetcherlar uchun interfeysdan foydalanishingiz kerak. Masalan:

CoroutineDispatchers interfeysi {
  val UI: dispetcher
  val IO: dispetcher
  val hisoblash: dispetcher
  fun newThread (val nomi: String): dispetcher
}

4. Ma'lumotni buzilishining oldini olish

To'xtatib turish funktsiyalariga ega bo'lmang, funktsiyadan tashqari ma'lumotlarni o'zgartiradi. Masalan, agar ikkita usul turli xil mavzularda ishlangan bo'lsa, unda ma'lumotlarning o'zgarishi mumkin emas:

val list = mutableListOf (1, 2)
kulgili yangilashni to'xtatishList1 () {
  ro'yxat [0] = ro'yxat [0] + 1
}
kulgili yangilashni to'xtatishList2 () {
  list.clear ()
}

Ushbu turdagi muammolardan qochishingiz mumkin:
- sizning kortuinlaringizga erishish va almashtirish o'rniga o'zgarmas buyumni qaytarib berish
- bu barcha korrintinlarni bitta tishli kontekstda ishga tushirish: newSingleThreadContext ("contextname")

5. Proguardni baxtli qiling

Ilovangizning tarkibiy qismlarini chiqarish uchun quyidagi qoidalarni kiritish kerak:

kotlinx.coroutines.internal.MainDispatcherFactory {} ismlar sinfi
kotlinx.coroutines.CoroutineExceptionHandler {} ismli sinflar
-klassmembernames class kotlinx. ** {o'zgaruvchan ; }

6. Java bilan o'zaro aloqa

Agar siz eski dasturda ishlayotgan bo'lsangiz, shubhasiz Java kodi juda ko'p bo'ladi. Siz Java-dan CompletableFuture-ni qaytarib korruintlarga qo'ng'iroq qilishingiz mumkin (kotlinx-coroutines-jdk8 artefaktini qo'shganingizga ishonch hosil qiling):

doSomethingAsync (): BajariladiganFuture > =
   GlobalScope.future {doSomething ()}

7. Kontekstda kerak bo'lmaydigan Retrofit

Agar siz "Retrofit coroutines" adapteridan foydalansangiz, kaput ostida okhttp-ning asink qo'ng'irog'ini ishlatadigan "Kechiktirilgan" degan nom olasiz. Shunday qilib, kodni IO ipida ishlashiga ishonch hosil qilish uchun RxJava-ga o'xshab, Context (Dispetchers.IO) -ni qo'shishingiz shart emas; agar siz "Retrofit coroutines" adapteridan foydalanmasangiz va "Retrofit Call" to'g'ridan-to'g'ri qo'ng'iroq qilsangiz, sizgaContext kerak bo'ladi.

Android Arch Component Room DB ham avtomatik ravishda UI bo'lmagan kontekstda ishlaydi, shuning uchun sizga Context kerak bo'lmaydi.

Adabiyotlar:

  • https://medium.com/capital-one-tech/kotlin-coroutines-on-android-things-i-wish-i-knew-at-the-beginning-c2f0b1f16cff
  • https://speakerdeck.com/elizarov/fresh-async-with-kotlin
  • https://medium.com/@michaelbukachi/coroutines-and-idling-resources-c1866bfa5b5d
  • https://blog.kotlin-academy.com/kotlin-coroutines-cheat-sheet-8cf1e284dc35
  • https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5?linkId=63267803
  • https://proandroiddev.com/managing-exceptions-in-nested-coroutine-scopes-9f23fd85e61