Jetpack MVVM七宗罪 之二:使用 luanchWhenX 啟動協程

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 的收集暫停了,但是上游的處理仍在繼續,資源浪費的問題解決地不夠徹底。

Jetpack MVVM七宗罪 之二:使用 luanchWhenX 啟動協程

資源浪費

舉一個資源浪費的例子,加深理解

fun FusedLocationProviderClient。locationFlow() = callbackFlow { val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult?) { result ?: return try { offer(result。lastLocation) } catch(e: Exception) {} } } // 持續獲取最新地理位置 requestLocationUpdates( createLocationRequest(), callback, Looper。getMainLooper())}

如上,使用

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 狀態時再啟動協程。從其命名上也可以直觀地認識這一點,即

圍繞某生命週期的進出反覆啟動新協程

Jetpack MVVM七宗罪 之二:使用 luanchWhenX 啟動協程

使用 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 Flow。flowWithLifecycle( lifecycle: Lifecycle, minActiveState: Lifecycle。State = Lifecycle。State。STARTED): Flow = callbackFlow { lifecycle。repeatOnLifecycle(minActiveState) { this@flowWithLifecycle。collect { send(it) } } close()}

最後

在這裡就還分享一份由大佬親自收錄整理的

Android學習PDF+架構影片+面試文件+原始碼筆記

高階架構技術進階腦圖、Android開發面試專題資料,高階進階架構資料

這些都是我現在閒暇時還會反覆翻閱的精品資料。裡面對近幾年的大廠面試高頻知識點都有詳細的講解。相信可以有效地幫助大家掌握知識、理解原理,幫助大家在未來取得一份不錯的答卷。

當然,你也可以拿去查漏補缺,提升自身的競爭力。

真心希望可以幫助到大家,Android路漫漫,共勉!

如果你有需要的話,只需

私信我【進階】即可獲取

Jetpack MVVM七宗罪 之二:使用 luanchWhenX 啟動協程

Jetpack MVVM七宗罪 之二:使用 luanchWhenX 啟動協程

Jetpack MVVM七宗罪 之二:使用 luanchWhenX 啟動協程