Android Kotlin Coroutine En İyi Uygulamaları

Android'de Kotlin Coroutines kullanmak için sürekli olarak sürdürülen en iyi uygulamalar kümesidir. Eklenmesi gereken herhangi bir konuda herhangi bir öneriniz varsa lütfen aşağıya yorum yapın.

  1. Android Yaşam Döngülerini Kullanma

CompositeDisposables ürününü RxJava ile kullandığınıza benzer şekilde, Kotlin Coroutines Faaliyetleri ve Bölümleri olan Android Livecycles farkındalığı ile doğru zamanda iptal edilmelidir.

a) Android View modellerini kullanmak

Bu, coroutinleri ayarlamanın en kolay yoludur, bu yüzden doğru zamanda kapanırlar, ancak sadece coroutine işlerinin güvenilir şekilde iptal edilebileceği onCleared işlevine sahip bir Android ViewModel içinde çalışır:

private val viewModelJob = İş ()
private val uiScope = CoroutineScope (Dispatchers.Main + görünümüModelJob)
eğlenceyi geçersiz kıl onCleared () {
 super.onCleared ()
 uiScope.coroutineContext.cancelChildren ()
}

Not: ViewModels 2.1.0-alpha01'den itibaren, bu artık gerekli değildir. Artık görünüm modelinizin CoroutineScope, onCleared uygulamasını yapmasına veya bir İş eklemesine gerek yok. Sadece “viewModelscope.launch {}” kullanın. 2.x'in, uygulamanızın AndroidX'te olması gerektiği anlamına geldiğini unutmayın, çünkü bunu ViewModels'in 1.x sürümüne desteklemeyi planladıklarından emin değilim.

b) Yaşam Döngüsü Gözlemcilerini Kullanma

Bu diğer teknik, bir faaliyete veya bölüme (veya bir Android Yaşam Döngüsü uygulayan başka bir şeye) eklediğiniz bir kapsam oluşturur:

/ **
 * Kullanıcı arayüzü yok edildiğinde otomatik olarak iptal edilen Coroutine içeriği
 * /
sınıf UiLifecycleScope: CoroutineScope, LifecycleObserver {

    Özel geç varit iş: iş
    val coroutineContext işlevini geçersiz kıl: CoroutineContext
        get () = iş + Satış memurları.

    @OnLifecycleEvent (Lifecycle.Event.ON_START)
    eğlenceli onCreate () {
        iş = İş ()
    }

    @OnLifecycleEvent (Lifecycle.Event.ON_PAUSE)
    fun destroy () = job.cancel ()
}
... içinde Destek Lib Etkinliği veya Parçası
private val uiScope = UiLifecycleScope ()
eğlenceyi geçersiz kıl onCreate (savedInstanceState: bundle) {
  super.onCreate (savedInstanceState)
  lifecycle.addObserver (uiScope)
}

c) GlobalScope

GlobalScope kullanıyorsanız, uygulamanın ömrünü uzatan bir kapsamdır. Bunu arka plan senkronizasyonu, repo yenilemeleri vb. Yapmak için kullanırsınız (bir Aktivite yaşam döngüsüne bağlı değildir).

d) Hizmetler

Hizmetler, onDestroy'da işlerini iptal edebilir:

private val serviceJob = İş ()
private val serviceScope = CoroutineScope (Sevk memuru.Main + serviceJob)
eğlenceyi geçersiz kıl onCleared () {
 super.onCleared ()
 serviceJob.cancel ()
}

2. İstisnaları Kullanma

a) Zaman uyumsuzunda, lansmana karşı runBlocking'de

Bir {} bloğundaki bir istisnaların istisnalar işleyicisi olmadan uygulamayı çökerteceğini unutmamak önemlidir. Daima başlatılacak bir parametre olarak iletilecek bir varsayılan istisna işleyici ayarlayın.

Bir runBlocking {} bloğu içindeki bir istisna, bir deneme yakalamak eklemediğiniz sürece uygulamayı çökertir. RunBlocking kullanıyorsanız, her zaman bir deneme / yakalama ekleyin. İdeal olarak, ünite testleri için sadece runBlocking kullanın.

Bir eşzamansız {} bloğuna atılan bir istisna, blok altında bekleneninceye kadar yayılmayacak veya çalıştırılmayacak çünkü altında gerçekten bir Java Ertelenmiş. Çağıran fonksiyon / yöntem istisnaları yakalamalıdır.

b) İstisnaları yakalamak

İstisnalar oluşturabilecek kodu çalıştırmak için async kullanıyorsanız, istisnaları doğru şekilde yakalamak için kodu bir coroutineScope'a sarmanız gerekir (örneğin, LouisC sayesinde):

Deneyin {
    coroutineScope {
        val mayFailAsync1 = zaman uyumsuz {
            mayFail1 ()
        }
        val mayFailAsync2 = zaman uyumsuz {
            mayFail2 ()
        }
        useResult (mayFailAsync1.await (), mayFailAsync2.await ())
    }
} catch (e: IOException) {
    // bunu yap
    MyIoException ("G / Ç yaparken hata", e)
} catch (e: AnotherException) {
    // bunu da hallet
    MyOtherException atmak ("bir şey yaparken hata", e)
}

İstisnayı yakaladığınızda, başka bir İstisna'ya sarın (RxJava için yaptığınıza benzer şekilde), böylece yalnızca koro kodlu bir kod içeren bir parça etiketi görmek yerine, yığın kod satırını kendi kodunuza getirin.

c) Günlük istisnaları

GlobalScope.launch veya bir aktör kullanıyorsanız, her zaman istisnaları günlüğe kaydedebilecek bir istisna eylemcisi iletin. Örneğin.

val errorHandler = CoroutineExceptionHandler {_, istisna ->
  // Crashlytics, logcat, vb.
}
val job = GlobalScope.launch (hataHandler) {
...
}

Neredeyse her zaman, Android'de kapsamları yapılandırmalı ve bir işleyici kullanılmalıdır:

val errorHandler = CoroutineExceptionHandler {_, istisna ->
  // Crashlytics, logcat, vb. giriş yapın; bağımlılık enjekte edilebilir
}
val supervisor = SupervisorJob () // Aktivite Yaşam Döngüsü ile iptal edildi
(CoroutineScope (coroutineContext + gözetmen)) ile {
  val bir şey = başlat (errorHandler) {
    ...
  }
}

Ayrıca, zaman uyumsuzluk kullanıyor ve bekliyorsanız, her zaman yukarıda açıklandığı şekilde deneyin / deneyin, ancak gerektiği gibi giriş yapın.

d) Sonuç / Hata Mühürlü Sınıfını düşünün

İstisnalar atmak yerine hataya neden olabilecek sonuç mühürlü bir sınıf kullanmayı düşünün:

mühürlü sınıf Sonuç  {
  data sınıfı Başarı (val data: T): Sonuç ()
  veri sınıfı Hata (val hatası: E): Sonuç ()
}

e) İsim Coroutine İçeriği

Bir eşzamansız lambda bildirirken, şöyle de adlandırabilirsiniz:

zaman uyumsuz (CoroutineName ("MyCoroutine"))) {}

Çalıştırmak üzere kendi iş parçanızı oluşturuyorsanız, bu iş parçacığını oluştururken ismini de verebilirsiniz:

newSingleThreadContext ( "MyCoroutineThread")

3. Executive Havuzları ve Varsayılan Havuz Boyutları

Coroutines, sınırlı bir iplik havuzu boyutunda gerçekten ortak bir görevdir (derleyici yardımı ile). Bu, eğer coroutininizde engelleyici bir şey yaparsanız (örneğin, engelleyici bir API kullanın), engelleme işlemi tamamlanıncaya kadar tüm ipliğin bağlanacağı anlamına gelir. Koroutin ayrıca bir verim veya gecikme yapmadığınız sürece askıya alınmaz, bu nedenle uzun bir işlem döngüsünüz varsa, koroutinin iptal edilip edilmediğini kontrol ettiğinizden emin olun (kapsamda “sureActive ()” arayın). iplik; Bu, RxJava'nın nasıl çalıştığına benzer.

Kotlin koroutinlerinde yerleşik birkaç dağıtım programı var (RxJava’daki programlayıcılara eşdeğer). Ana gönderici (çalıştırılacak bir şey belirtmezseniz) UI'dir; UI öğelerini yalnızca bu bağlamda değiştirmelisiniz. Ayrıca UI ve arka plan iş parçacıkları arasında zıplayabilen ve tek bir iş parçacığında olmayan bir Dispatchers.Unconfined; bu genel olarak ünite testleri dışında kullanılmamalıdır. GÇ yönetimi için bir Dispatchers.IO var (sık sık askıya alınan ağ aramaları). Son olarak, ana arka plan iş parçacığı havuzu olan bir Dispatchers.Default var, ancak bu CPU sayısı ile sınırlıdır.

Uygulamada, sınıfın kurucusu aracılığıyla iletilen ortak göndericiler için bir arayüz kullanmanız gerekir; böylece test için farklı olanları değiştirebilirsiniz. Örneğin.:

arayüz CoroutineDispatchers {
  val UI: Gönderici
  val IO: Gönderici
  val Hesaplama: Gönderici
  fun newThread (val name: String): Gönderici
}

4. Veri Yolsuzluğundan Kaçınmak

Askıya alma işlevleri, işlev dışındaki verileri değiştirmez. Örneğin, iki yöntem farklı iş parçacıklarından çalıştırıldığında, istenmeyen veri değişikliği olabilir:

val list = mutableListOf (1, 2)
askıya alma eğlenceli updateList1 () {
  liste [0] = liste [0] + 1
}
eğlenceli updateList2 askıya almak () {
  list.clear ()
}

Bu tür sorunlardan kaçınabilirsiniz:
- coroutinlerinizin uzanarak ve onu değiştirmek yerine değişmez bir nesne döndürmesini sağlamak
- tüm bu coroutine'leri aşağıdakiler aracılığıyla oluşturulan tek bir dişli bağlamda çalıştırın: newSingleThreadContext (“contextname”)

5. Proguard Mutlu Olun

Bu, uygulamanızın sürüm sürümleri için kuralların eklenmesi gerekir:

-keepnames sınıfı kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames sınıfı kotlinx.coroutines.CoroutineExceptionHandler {}
-keepclassmembernames sınıf kotlinx. ** {uçucu ; }

6. Java ile birlikte çalışma

Eski bir uygulama üzerinde çalışıyorsanız, kayda değer miktarda Java kodunuz olduğundan şüpheniz yoktur. Bir CompletableFuture döndürerek coroutines'i Java'dan çağırabilirsiniz (kotlinx-coroutines-jdk8 eserini eklediğinizden emin olun):

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

7. Güçlendirme, Context ile İhtiyacınız Yok

Retrofit coroutines adaptörünü kullanıyorsanız, okhttp’in kaputunun altındaki async çağrısını kullanan bir Ertelenmiş elde edersiniz. Bu nedenle, kodun bir IO iş parçacığında çalıştığından emin olmak için RxJava ile yapmak zorunda olduğunuz gibi Context (Dispatchers.IO) eklemeniz gerekmez; Retrofit coroutines adaptörünü kullanmıyorsanız ve doğrudan bir Retrofit Çağrısı kullanıyorsanız, Context ile ihtiyacınız vardır.

Android Arch Components Room DB ayrıca otomatik olarak UI dışı bir bağlamda da çalışır, bu nedenle Contont ile ihtiyacınız yoktur.

Referanslar:

  • 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