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í)行的:
- 父協(xié)程的協(xié)程調(diào)度器是處于Dispatchers.Main情況下啟動。
- 同時子協(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é)程執(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啟動時:
- 無論我們是否指定協(xié)程調(diào)度器,掛起前的執(zhí)行都是在當(dāng)前線程下執(zhí)行。
- 如果所在的協(xié)程沒有指定調(diào)度器,那么就會在join處恢復(fù)執(zhí)行的線程里執(zhí)行,即我們上述案例中的掛起后的執(zhí)行是在main線程中執(zhí)行。
- 當(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將被取消。