kotlin & Coroutine

RxJava

subscribeOn()和observeOn()的區(qū)別

subscribeOn()和observeOn()都是用來切換線程用的

  • subscribeOn()改變調(diào)用它之前(及之后,如果前面沒有指定observeOn的話)代碼的線程
  • observeOn()改變調(diào)用它之后代碼的線程
  • doOnSubscribe()之后如果指定了subscribeOn,doOnSubscribe就在指定的線程上運行,如果沒指定就運行在subscribe方法被調(diào)用時的線程上。
  • doOnSubscribe()之前的subscribeOn()只有第一次使用的會生效。

Coroutine

協(xié)程是采用就是并發(fā)的設(shè)計模式,這句話的大多數(shù)環(huán)境下是沒有問題。但是,如果某個協(xié)程滿足以下幾點,那它里面的子協(xié)程將會是同步執(zhí)行的:

  1. 父協(xié)程的協(xié)程調(diào)度器是處于Dispatchers.Main情況下啟動。
  2. 同時子協(xié)程在不修改協(xié)程調(diào)度器下的情況下啟動。
private fun start() {
    GlobalScope.launch(Dispatchers.Main) {
        for (index in 1 until  10) {
            //同步執(zhí)行
            launch {
                Log.d("launch$index", "啟動一個協(xié)程")
            }
        }
    }
}

Android平臺上如果協(xié)程處于Dispatchers.Main調(diào)度器,它會將協(xié)程調(diào)度到UI事件循環(huán)中執(zhí)行。
如果其中的某一個子協(xié)程將他的協(xié)程調(diào)度器修改為非Dispatchers.Main,那么這個子協(xié)程將會與其他子協(xié)程并發(fā)執(zhí)行。

協(xié)程狀態(tài)

協(xié)程生命周期

父協(xié)程需要等待所有的子協(xié)程執(zhí)行完畢之后才會進(jìn)入Completed狀態(tài),不管父協(xié)程自身的協(xié)程體是否已經(jīng)執(zhí)行完成。

協(xié)程調(diào)度器

CoroutineDispatcher確定了相關(guān)的協(xié)程在哪個線程或哪些線程上執(zhí)行。協(xié)程調(diào)度器可以將協(xié)程限制在一個特定的線程執(zhí)行,或?qū)⑺峙傻揭粋€線程池,亦或是讓它不受限地運行。

  • withContext頂級函數(shù),使用withContext函數(shù)來改變協(xié)程的上下文,而仍然駐留在相同的協(xié)程中,同時withContext還攜帶有一個泛型T返回值。

協(xié)程上下文

  • CoroutineContext即協(xié)程上下文。它是一個包含了用戶定義的一些各種不同元素的Element對象集合。其中主要元素是Job、協(xié)程調(diào)度器CoroutineDispatcher、還有包含協(xié)程異常CoroutineExceptionHandler、攔截器ContinuationInterceptor、協(xié)程名CoroutineName等。這些數(shù)據(jù)都是和協(xié)程密切相關(guān)的,每一個Element都一個唯一key。
  • 子協(xié)程會繼承父協(xié)程的協(xié)程上下文中的Element,如果自身有相同key的成員,則覆蓋對應(yīng)的key,覆蓋的效果僅限自身范圍內(nèi)有效。

協(xié)程啟動模式

CoroutineStart協(xié)程啟動模式,是啟動協(xié)程時需要傳入的第二個參數(shù)。協(xié)程啟動有4種:

  • DEFAULT 默認(rèn)啟動模式,我們可以稱之為餓漢啟動模式,因為協(xié)程創(chuàng)建后立即開始調(diào)度,雖然是立即調(diào)度,單不是立即執(zhí)行,有可能在執(zhí)行前被取消。
  • LAZY 懶漢啟動模式,啟動后并不會有任何調(diào)度行為,直到我們需要它執(zhí)行的時候才會產(chǎn)生調(diào)度。也就是說只有我們主動的調(diào)用Job的start、join或者await等函數(shù)時才會開始調(diào)度。
  • ATOMIC 一樣也是在協(xié)程創(chuàng)建后立即開始調(diào)度,但是它和DEFAULT模式有一點不一樣,通過ATOMIC模式啟動的協(xié)程執(zhí)行到第一個掛起點之前是不響應(yīng)cancel 取消操作的,ATOMIC一定要涉及到協(xié)程掛起后cancel 取消操作的時候才有意義。
  • UNDISPATCHED 協(xié)程在這種模式下會直接開始在當(dāng)前線程下執(zhí)行,直到運行到第一個掛起點。這聽起來有點像 ATOMIC,不同之處在于UNDISPATCHED是不經(jīng)過任何調(diào)度器就開始執(zhí)行的。當(dāng)然遇到掛起點之后的執(zhí)行,將取決于掛起點本身的邏輯和協(xié)程上下文中的調(diào)度器。

可以總結(jié)一下,當(dāng)以UNDISPATCHED啟動時:

  1. 無論我們是否指定協(xié)程調(diào)度器,掛起前的執(zhí)行都是在當(dāng)前線程下執(zhí)行。
  2. 如果所在的協(xié)程沒有指定調(diào)度器,那么就會在join處恢復(fù)執(zhí)行的線程里執(zhí)行,即我們上述案例中的掛起后的執(zhí)行是在main線程中執(zhí)行。
  3. 當(dāng)我們指定了協(xié)程調(diào)度器時,遇到掛起點之后的執(zhí)行將取決于掛起點本身的邏輯和協(xié)程上下文中的調(diào)度器。即join處恢復(fù)執(zhí)行時,因為所在的協(xié)程有調(diào)度器,所以后面的執(zhí)行將會在調(diào)度器對應(yīng)的線程上執(zhí)行。

協(xié)程作用域

協(xié)程作用域分為三種:

  • 頂級作用域 --> 沒有父協(xié)程的協(xié)程所在的作用域稱之為頂級作用域。
  • 協(xié)同作用域 --> 在協(xié)程中啟動一個協(xié)程,新協(xié)程為所在協(xié)程的子協(xié)程。子協(xié)程所在的作用域默認(rèn)為協(xié)同作用域。此時子協(xié)程拋出未捕獲的異常時,會將異常傳遞給父協(xié)程處理,如果父協(xié)程被取消,則所有子協(xié)程同時也會被取消。
  • 主從作用域 官方稱之為監(jiān)督作用域。與協(xié)同作用域一致,區(qū)別在于該作用域下的協(xié)程取消操作的單向傳播性,子協(xié)程的異常不會導(dǎo)致其它子協(xié)程取消。但是如果父協(xié)程被取消,則所有子協(xié)程同時也會被取消。

Flow

  • flowOn只影響前面沒有自己上下文的操作符。
  • 不管flowOn如何切換線程,collect始終運行在調(diào)用它的協(xié)程調(diào)度器上。

Flow的常用操作符

  • 流程操作符:onStart/onEach/onCompletion
  • 異常操作符:catch
  • 轉(zhuǎn)換操作符:transform/map/filter/flatMapConcat/flatmapMerge/flatMapLatest
  • 限制操作符:drop/take
  • 末端操作符:collect/toList/single

onCompletion:在流程完成或取消后調(diào)用,并將取消異常或失敗作為操作的原因參數(shù)傳遞。
transform操作符任意值任意次,其他轉(zhuǎn)換操作符都是基于transform進(jìn)行擴(kuò)展的。

Flow的緩沖

buffer操作符,場景:上游生產(chǎn)速度比下游消費速度快。使用buffer緩存上游發(fā)射的數(shù)據(jù),下游可以直接取用。壓縮了整個流的處理時長。

Flow的內(nèi)存泄漏

Flow無法像LiveData那樣感知生命周期。
感知生命周期為LiveData至少帶來兩個好處:

避免泄漏:當(dāng) lifecycleOwner 進(jìn)入 DESTROYED 時,會自動刪除 Observer
節(jié)省資源:當(dāng) lifecycleOwner 進(jìn)入 STARTED 時才開始接受數(shù)據(jù),避免 UI 處于后臺時的無效計算。

lifecycleScope
lifecycleOwner.lifecycleScope 擴(kuò)展,可以在當(dāng)前 Activity 或 Fragment 銷毀時結(jié)束此協(xié)程,防止泄露。
Flow 也是運行在協(xié)程中的,lifecycleScope 可以幫助 Flow 解決內(nèi)存泄露的問題:

lifecycleScope.launch {
    viewMode.stateFlow.collect { 
       updateUI(it)
    }
}

雖然解決了內(nèi)存泄漏問題, 但是 lifecycleScope.launch 會立即啟動協(xié)程,之后一直運行直到協(xié)程銷毀,無法像 LiveData 僅當(dāng) UI 處于前臺才執(zhí)行,對資源的浪費比較大。

因此,lifecycle-runtime-ktx 又為我們提供了 LaunchWhenStarted 和 LaunchWhenResumed ( 下文統(tǒng)稱為 LaunchWhenX )

launchWhenX 的利與弊
LaunchWhenX 會在 lifecycleOwner 進(jìn)入 X 狀態(tài)之前一直等待,又在離開 X 狀態(tài)時掛起協(xié)程。lifecycleScope + launchWhenX 的組合終于使 Flow 有了與 LiveData 相媲美的生命周期可感知能力:

避免泄露:當(dāng) lifecycleOwner 進(jìn)入 DESTROYED 時, lifecycleScope 結(jié)束協(xié)程
節(jié)省資源:當(dāng) lifecycleOwner 進(jìn)入 STARTED/RESUMED 時 launchWhenX 恢復(fù)執(zhí)行,否則掛起。

但對于 launchWhenX 來說, 當(dāng) lifecycleOwner 離開 X 狀態(tài)時,協(xié)程只是掛起協(xié)程而非銷毀,如果用這個協(xié)程來訂閱 Flow,就意味著雖然 Flow 的收集暫停了,但是上游的處理仍在繼續(xù),資源浪費的問題解決地不夠徹底。
即使在 launchWhenX 中訂閱 Flow 仍然是不夠的,無法完全避免資源的浪費。
解決辦法:repeatOnLifecycle
lifecycle-runtime-ktx 自 2.4.0-alpha01 起,提供了一個新的協(xié)程構(gòu)造器 lifecyle.repeatOnLifecycle, 它在離開 X 狀態(tài)時銷毀協(xié)程,再進(jìn)入 X 狀態(tài)時再啟動協(xié)程。從其命名上也可以直觀地認(rèn)識這一點,即圍繞某生命周期的進(jìn)出反復(fù)啟動新協(xié)程。
使用 repeatOnLifecycle 可以彌補上述 launchWhenX 對協(xié)程僅掛起而不銷毀的弊端。因此,正確訂閱 Flow 的寫法應(yīng)該如下(以在 Fragment 中為例):

onCreateView(...) {
    viewLifecycleOwner.lifecycleScope.launch {
        viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {
            viewMode.stateFlow.collect { ... }
        }
    }
}

當(dāng) Fragment 處于 STARTED 狀態(tài)時會開始收集數(shù)據(jù),并且在 RESUMED 狀態(tài)時保持收集,最終在 Fragment 進(jìn)入 STOPPED 狀態(tài)時結(jié)束收集過程。

需要注意 repeatOnLifecycle 本身是個掛起函數(shù),一旦被調(diào)用,將走不到后續(xù)代碼,除非 lifecycle 進(jìn)入 DESTROYED。

最后:Flow.flowWithLifecycle
當(dāng)我們只有一個 Flow 需要收集時,可以使用 flowWithLifecycle 這樣一個 Flow 操作符的形式來簡化代碼

lifecycleScope.launch {
     viewMode.stateFlow
          .flowWithLifecycle(this, Lifecycle.State.STARTED)
          .collect { ... }
 }

當(dāng)然,其本質(zhì)還是對 repeatOnLifecycle 的封裝:

public fun <T> Flow<T>.flowWithLifecycle(
    lifecycle: Lifecycle,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED
): Flow<T> = callbackFlow {
    lifecycle.repeatOnLifecycle(minActiveState) {
        this@flowWithLifecycle.collect {
            send(it)
        }
    }
    close()
}

MMM

  • flatMapConcat
    下游阻塞上游的數(shù)據(jù)發(fā)射,所有數(shù)據(jù)串行發(fā)射。

  • flatMapMerge
    下游可以并發(fā)地收集上游數(shù)據(jù)。

  • flatMapLatest
    上游發(fā)射的數(shù)據(jù)被buffer收集發(fā)送給下游,下游接收到上游的數(shù)據(jù)時如果下游處于block狀態(tài),下游的block將被取消。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容