Flow vs LiveData
自 StateFlow/ SharedFlow 出現(xiàn)后纫骑, 官方開(kāi)始推薦在 MVVM 中使用 Flow 替換 LiveData辉懒。 見(jiàn)文章:https://juejin.cn/post/6979008878029570055
Flow 基于協(xié)程實(shí)現(xiàn)渗蟹,具有豐富的操作符僧叉,通過(guò)這些操作符可以實(shí)現(xiàn)線程切換、處理流式數(shù)據(jù)哥谷,相比 LiveData 功能更加強(qiáng)大岸夯。 但唯有一點(diǎn)不足,無(wú)法像 LiveData 那樣感知生命周期们妥。
感知生命周期為 LiveData 至少帶來(lái)以下兩個(gè)好處:
- 避免泄漏:當(dāng) lifecycleOwner 進(jìn)入 DESTROYED 時(shí)猜扮,會(huì)自動(dòng)刪除 Observer
- 節(jié)省資源:當(dāng) lifecycleOwner 進(jìn)入 STARTED 時(shí)才開(kāi)始接受數(shù)據(jù),避免 UI 處于后臺(tái)時(shí)的無(wú)效計(jì)算监婶。
Flow 也需要做到上面兩點(diǎn)旅赢,才能真正地替代 LiveData。
lifecycleScope
lifecycle-runtime-ktx
庫(kù)提供了 lifecycleOwner.lifecycleScope
擴(kuò)展,可以在當(dāng)前 Activity 或 Fragment 銷(xiāo)毀時(shí)結(jié)束此協(xié)程鲜漩,防止泄露源譬。
Flow 也是運(yùn)行在協(xié)程中的集惋,lifecycleScope
可以幫助 Flow 解決內(nèi)存泄露的問(wèn)題:
lifecycleScope.launch {
viewMode.stateFlow.collect {
updateUI(it)
}
}
雖然解決了內(nèi)存泄漏問(wèn)題孕似, 但是 lifecycleScope.launch
會(huì)立即啟動(dòng)協(xié)程,之后一直運(yùn)行直到協(xié)程銷(xiāo)毀刮刑,無(wú)法像 LiveData 僅當(dāng) UI 處于前臺(tái)才執(zhí)行喉祭,對(duì)資源的浪費(fèi)比較大。
因此雷绢,lifecycle-runtime-ktx
又為我們提供了 LaunchWhenStarted
和 LaunchWhenResumed
( 下文統(tǒng)稱為 LaunchWhenX
)
launchWhenX 的利與弊
LaunchWhenX
會(huì)在 lifecycleOwner 進(jìn)入 X 狀態(tài)之前一直等待泛烙,又在離開(kāi) X 狀態(tài)時(shí)掛起協(xié)程。 lifecycleScope + launchWhenX 的組合終于使 Flow 有了與 LiveData 相媲美的生命周期可感知能力:
- 避免泄露:當(dāng) lifecycleOwner 進(jìn)入 DESTROYED 時(shí)翘紊, lifecycleScope 結(jié)束協(xié)程
- 節(jié)省資源:當(dāng) lifecycleOwner 進(jìn)入 STARTED/RESUMED 時(shí) launchWhenX 恢復(fù)執(zhí)行蔽氨,否則掛起。
但對(duì)于 launchWhenX 來(lái)說(shuō)帆疟, 當(dāng) lifecycleOwner 離開(kāi) X 狀態(tài)時(shí)鹉究,協(xié)程只是掛起協(xié)程而非銷(xiāo)毀,如果用這個(gè)協(xié)程來(lái)訂閱 Flow踪宠,就意味著雖然 Flow 的收集暫停了自赔,但是上游的處理仍在繼續(xù),資源浪費(fèi)的問(wèn)題解決地不夠徹底柳琢。
資源浪費(fèi)
舉一個(gè)資源浪費(fèi)的例子绍妨,加深理解
fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> {
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
result ?: return
try { offer(result.lastLocation) } catch(e: Exception) {}
}
}
// 持續(xù)獲取最新地理位置
requestLocationUpdates(
createLocationRequest(), callback, Looper.getMainLooper())
}
如上,使用 callbackFlow
封裝了一個(gè) GoogleMap 中獲取位置的服務(wù)柬脸,requestLocationUpdates
實(shí)時(shí)獲取最新位置他去,并通過(guò) Flow 返回
class LocationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 進(jìn)入 STATED 時(shí),collect 開(kāi)始接收數(shù)據(jù)
// 進(jìn)入 STOPED 時(shí)倒堕,collect 掛起
lifecycleScope.launchWhenStarted {
locationProvider.locationFlow().collect {
// Update the UI
}
}
}
}
當(dāng) LocationActivity
進(jìn)入 STOPED
時(shí)灾测, lifecycleScope.launchWhenStarted
掛起,停止接受 Flow 的數(shù)據(jù)涩馆,UI 也隨之停止更新行施。但是 callbackFlow
中的 requestLocationUpdates
仍然還在持續(xù),造成資源的浪費(fèi)魂那。
因此蛾号,即使在 launchWhenX 中訂閱 Flow 仍然是不夠的,無(wú)法完全避免資源的浪費(fèi)
解決辦法:repeatOnLifecycle
lifecycle-runtime-ktx 自 2.4.0-alpha01
起涯雅,提供了一個(gè)新的協(xié)程構(gòu)造器 lifecyle.repeatOnLifecycle
鲜结, 它在離開(kāi) X 狀態(tài)時(shí)銷(xiāo)毀協(xié)程,再進(jìn)入 X 狀態(tài)時(shí)再啟動(dòng)協(xié)程。從其命名上也可以直觀地認(rèn)識(shí)這一點(diǎn)精刷,即圍繞某生命周期的進(jìn)出反復(fù)啟動(dòng)新協(xié)程拗胜。
使用 repeatOnLifecycle 可以彌補(bǔ)上述 launchWhenX 對(duì)協(xié)程僅掛起而不銷(xiāo)毀的弊端。因此怒允,正確訂閱 Flow 的寫(xiě)法應(yīng)該如下(以在 Fragment 中為例):
onCreateView(...) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {
viewMode.stateFlow.collect { ... }
}
}
}
當(dāng) Fragment 處于 STARTED 狀態(tài)時(shí)會(huì)開(kāi)始收集數(shù)據(jù)埂软,并且在 RESUMED 狀態(tài)時(shí)保持收集,最終在 Fragment 進(jìn)入 STOPPED 狀態(tài)時(shí)結(jié)束收集過(guò)程纫事。
需要注意 repeatOnLifecycle 本身是個(gè)掛起函數(shù)勘畔,一旦被調(diào)用,將走不到后續(xù)代碼丽惶,除非 lifecycle 進(jìn)入 DESTROYED炫七。
順道提一點(diǎn),前面舉得地圖SDK的例子是個(gè)冷流的例子钾唬,對(duì)于熱流(StateFlow/SharedFlow)是否有必要使用 repeatOnLifecycle 呢万哪? 個(gè)人認(rèn)為熱流的使用場(chǎng)景中,像前面例子那樣的情況會(huì)少一些抡秆,但是在 StateFlow/SharedFlow 的實(shí)現(xiàn)中奕巍,需要為每個(gè) FlowCollector
分配一些資源,如果 FlowCollector
能即使銷(xiāo)毀也是有利的琅轧,同時(shí)為了保持寫(xiě)法的統(tǒng)一伍绳,無(wú)論冷流熱流都建議使用 repeatOnLifecycle
最后:Flow.flowWithLifecycle
當(dāng)我們只有一個(gè) Flow 需要收集時(shí),可以使用 flowWithLifecycle
這樣一個(gè) Flow 操作符的形式來(lái)簡(jiǎn)化代碼
lifecycleScope.launch {
viewMode.stateFlow
.flowWithLifecycle(this, Lifecycle.State.STARTED)
.collect { ... }
}
當(dāng)然乍桂,其本質(zhì)還是對(duì) 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()
}