Flow vs LiveData
自 StateFlow/ SharedFlow 出現後, 官方開始推薦在 MVVM 中使用 Flow 替換 LiveData 。
Flow 基於協程實現,具有豐富的運算子,透過這些運算子可以實現執行緒切換、處理流式資料,相比 LiveData 功能更加強大。但唯有一點不足,無法像 LiveData 那樣感知生命週期。
感知生命週期為 LiveData 至少帶來以下兩個好處:
避免洩漏:當 lifecycleOwner 進入 DESTROYED 時,會自動刪除 Observer
節省資源:當 lifecycleOwner 進入 STARTED 時才開始接受資料,避免 UI 處於後臺時的無效計算。
Flow 也需要做到上面兩點,才能真正地替代 LiveData。
lifecycleScope
lifecycle-runtime-ktx
庫提供了
lifecycleOwner。lifecycleScope
擴充套件,可以在當前 Activity 或 Fragment 銷燬時結束此協程,防止洩露。
Flow 也是執行在協程中的,
lifecycleScope
可以幫助 Flow 解決記憶體洩露的問題:
lifecycleScope。launch { viewMode。stateFlow。collect { updateUI(it) }}
雖然解決了記憶體洩漏問題, 但是
lifecycleScope。launch
會立即啟動協程,之後一直執行直到協程銷燬,無法像 LiveData 僅當 UI 處於前臺才執行,對資源的浪費比較大。
因此,
lifecycle-runtime-ktx
又為我們提供了
LaunchWhenStarted
和
LaunchWhenResumed
( 下文統稱為
LaunchWhenX
)
launchWhenX 的利與弊
LaunchWhenX
會在 lifecycleOwner 進入 X 狀態之前一直等待,又在離開 X 狀態時掛起協程。lifecycleScope + launchWhenX 的組合終於使 Flow 有了與 LiveData 相媲美的生命週期可感知能力:
避免洩露:當 lifecycleOwner 進入 DESTROYED 時, lifecycleScope 結束協程
節省資源:當 lifecycleOwner 進入 STARTED/RESUMED 時 launchWhenX 恢復執行,否則掛起。
但對於 launchWhenX 來說, 當 lifecycleOwner 離開 X 狀態時,協程只是掛起協程而非銷燬,如果用這個協程來訂閱 Flow,就意味著雖然 Flow 的收集暫停了,但是上游的處理仍在繼續,資源浪費的問題解決地不夠徹底。
資源浪費
舉一個資源浪費的例子,加深理解
fun FusedLocationProviderClient。locationFlow() = callbackFlow
如上,使用
callbackFlow
封裝了一個 GoogleMap 中獲取位置的服務,
requestLocationUpdates
實時獲取最新位置,並透過 Flow 返回
class LocationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super。onCreate(savedInstanceState) // 進入 STATED 時,collect 開始接收資料 // 進入 STOPED 時,collect 掛起 lifecycleScope。launchWhenStarted { locationProvider。locationFlow()。collect { // Update the UI } } }}
當
LocationActivity
進入
STOPED
時,
lifecycleScope。launchWhenStarted
掛起,停止接受 Flow 的資料,UI 也隨之停止更新。但是
callbackFlow
中的
requestLocationUpdates
仍然還在持續,造成資源的浪費。
因此,
即使在 launchWhenX 中訂閱 Flow 仍然是不夠的,無法完全避免資源的浪費
解決辦法:repeatOnLifecycle
lifecycle-runtime-ktx 自
2。4。0-alpha01
起,提供了一個新的協程構造器
lifecyle。repeatOnLifecycle
, 它在離開 X 狀態時銷燬協程,再進入 X 狀態時再啟動協程。從其命名上也可以直觀地認識這一點,即
圍繞某生命週期的進出反覆啟動新協程
。
使用 repeatOnLifecycle 可以彌補上述 launchWhenX 對協程僅掛起而不銷燬的弊端。因此,正確訂閱 Flow 的寫法應該如下(以在 Fragment 中為例):
onCreateView(。。。) { viewLifecycleOwner。lifecycleScope。launch { viewLifecycleOwner。lifecycle。repeatOnLifecycle(STARTED) { viewMode。stateFlow。collect { 。。。 } } }}
當 Fragment 處於 STARTED 狀態時會開始收集資料,並且在 RESUMED 狀態時保持收集,最終在 Fragment 進入 STOPPED 狀態時結束收集過程。
需要注意 repeatOnLifecycle 本身是個掛起函式,一旦被呼叫,將走不到後續程式碼,除非 lifecycle 進入 DESTROYED。
冷流 or 熱流
順道提一點,前面舉得地圖SDK的例子是個冷流的例子,對於熱流(StateFlow/SharedFlow)是否有必要使用 repeatOnLifecycle 呢?個人認為熱流的使用場景中,像前面例子那樣的情況會少一些,但是在 StateFlow/SharedFlow 的實現中,需要為每個
FlowCollector
分配一些資源,如果
FlowCollector
能即使銷燬也是有利的,同時為了保持寫法的統一,無論冷流熱流都建議使用
repeatOnLifecycle
最後:Flow。flowWithLifecycle
當我們只有一個 Flow 需要收集時,可以使用
flowWithLifecycle
這樣一個 Flow 運算子的形式來簡化程式碼
lifecycleScope。launch { viewMode。stateFlow 。flowWithLifecycle(this, Lifecycle。State。STARTED) 。collect { 。。。 } }
當然,其本質還是對
repeatOnLifecycle
的封裝:
public fun
最後
在這裡就還分享一份由大佬親自收錄整理的
Android學習PDF+架構影片+面試文件+原始碼筆記
,
高階架構技術進階腦圖、Android開發面試專題資料,高階進階架構資料
這些都是我現在閒暇時還會反覆翻閱的精品資料。裡面對近幾年的大廠面試高頻知識點都有詳細的講解。相信可以有效地幫助大家掌握知識、理解原理,幫助大家在未來取得一份不錯的答卷。
當然,你也可以拿去查漏補缺,提升自身的競爭力。
真心希望可以幫助到大家,Android路漫漫,共勉!
如果你有需要的話,只需
私信我【進階】即可獲取