Android Flow 與Live Data對(duì)比

[轉(zhuǎn)]官方推薦 Flow 取代 LiveData,有必要嗎?
更加詳細(xì)的文章:不做跟風(fēng)黨,LiveData,StateFlow,SharedFlow 的使用場(chǎng)景對(duì)比

前言

打開(kāi)Android架構(gòu)組件頁(yè)面,我們可以發(fā)現(xiàn)一些最新發(fā)布的jetpack組件,如Room,DataStore, Paging3,DataBinding 等都支持了Flow
Google開(kāi)發(fā)者賬號(hào)最近也發(fā)布了幾篇使用Flow的文章,比如:從 LiveData 遷移到 Kotlin 數(shù)據(jù)流
看起來(lái)官方在大力推薦使用Flow取代LiveData,那么問(wèn)題來(lái)了,有必要嗎?
LiveData用得好好的,有必要再學(xué)Flow嗎?本文主要回答這個(gè)問(wèn)題,具體包括以下內(nèi)容
1.LiveData有什么不足?
2.Flow介紹以及為什么會(huì)有Flow
3.SharedFlowStateFlow的介紹與它們之間的區(qū)別

本文具體目錄如下所示:


image.png

1. LiveData有什么不足?

要了解LiveData的不足,我們先了解下LiveData為什么被引入

LiveData 的歷史要追溯到 2017 年。彼時(shí),觀察者模式有效簡(jiǎn)化了開(kāi)發(fā),但諸如 RxJava 一類(lèi)的庫(kù)對(duì)新手而言有些太過(guò)復(fù)雜。為此,架構(gòu)組件團(tuán)隊(duì)打造了LiveData: 一個(gè)專(zhuān)用于 Android 的具備自主生命周期感知能力的可觀察的數(shù)據(jù)存儲(chǔ)器類(lèi)。LiveData 被有意簡(jiǎn)化設(shè)計(jì),這使得開(kāi)發(fā)者很容易上手;而對(duì)于較為復(fù)雜的交互數(shù)據(jù)流場(chǎng)景,建議您使用 RxJava,這樣兩者結(jié)合的優(yōu)勢(shì)就發(fā)揮出來(lái)了

可以看出,LiveData就是一個(gè)簡(jiǎn)單易用的,具備感知生命周期能力的觀察者模式
它使用起來(lái)非常簡(jiǎn)單,這是它的優(yōu)點(diǎn),也是它的不足,因?yàn)樗鎸?duì)比較復(fù)雜的交互數(shù)據(jù)流場(chǎng)景時(shí),處理起來(lái)比較麻煩

1.2 LiveData的不足

我們上文說(shuō)過(guò)LiveData結(jié)構(gòu)簡(jiǎn)單,但是不夠強(qiáng)大,它有以下不足

  1. LiveData只能在主線(xiàn)程更新數(shù)據(jù)
  2. LiveData的操作符不夠強(qiáng)大,在處理復(fù)雜數(shù)據(jù)流時(shí)有些捉襟見(jiàn)肘

關(guān)于LiveData只能在主線(xiàn)程更新數(shù)據(jù),有的同學(xué)可能要問(wèn),不是有postValue嗎?其實(shí)postValue也是需要切換到到主線(xiàn)程的,如下圖所示:

image.png

這意味著當(dāng)我們想要更新LiveData對(duì)象時(shí),我們會(huì)經(jīng)常更改線(xiàn)程(工作線(xiàn)程→主線(xiàn)程),如果在修改LiveData后又要切換回到工作線(xiàn)程那就更麻煩了,同時(shí)postValue可能會(huì)有丟數(shù)據(jù)的問(wèn)題。

2. Flow介紹

Flow 就是 Kotlin 協(xié)程與響應(yīng)式編程模型結(jié)合的產(chǎn)物,你會(huì)發(fā)現(xiàn)它與 RxJava 非常像,二者之間也有相互轉(zhuǎn)換的 API,使用起來(lái)非常方便。

2.1 為什么引入Flow

為什么引入Flow,我們可以從Flow解決了什么問(wèn)題的角度切入

  1. LiveData不支持線(xiàn)程切換,所有數(shù)據(jù)轉(zhuǎn)換都將在主線(xiàn)程上完成,有時(shí)需要頻繁更改線(xiàn)程,面對(duì)復(fù)雜數(shù)據(jù)流時(shí)處理起來(lái)比較麻煩
  2. RxJava又有些過(guò)于麻煩了,有許多讓人傻傻分不清的操作符,入門(mén)門(mén)檻較高,同時(shí)需要自己處理生命周期,在生命周期結(jié)束時(shí)取消訂閱

可以看出,Flow是介于LiveDataRxJava之間的一個(gè)解決方案,它有以下特點(diǎn)

  • Flow 支持線(xiàn)程切換、背壓
  • Flow 入門(mén)的門(mén)檻很低,沒(méi)有那么多傻傻分不清楚的操作符
  • 簡(jiǎn)單的數(shù)據(jù)轉(zhuǎn)換與操作符,如 map 等等
  • 冷數(shù)據(jù)流,不消費(fèi)則不生產(chǎn)數(shù)據(jù),這一點(diǎn)與LiveData不同:LiveData的發(fā)送端并不依賴(lài)于接收端。
  • 屬于kotlin協(xié)程的一部分,可以很好的與協(xié)程基礎(chǔ)設(shè)施結(jié)合

關(guān)于Flow的使用,比較簡(jiǎn)單,有興趣的同學(xué)可參閱文檔:Flow文檔

3. SharedFlow介紹

我們上面介紹過(guò),Flow 是冷流,什么是冷流?

  • 冷流 :只有訂閱者訂閱時(shí),才開(kāi)始執(zhí)行發(fā)射數(shù)據(jù)流的代碼。并且冷流和訂閱者只能是一對(duì)一的關(guān)系,當(dāng)有多個(gè)不同的訂閱者時(shí),消息是重新完整發(fā)送的。也就是說(shuō)對(duì)冷流而言,有多個(gè)訂閱者的時(shí)候,他們各自的事件是獨(dú)立的。
  • 熱流:無(wú)論有沒(méi)有訂閱者訂閱,事件始終都會(huì)發(fā)生。當(dāng) 熱流有多個(gè)訂閱者時(shí),熱流與訂閱者們的關(guān)系是一對(duì)多的關(guān)系,可以與多個(gè)訂閱者共享信息。

3.1 為什么引入SharedFlow

上面其實(shí)已經(jīng)說(shuō)得很清楚了,冷流和訂閱者只能是一對(duì)一的關(guān)系,當(dāng)我們要實(shí)現(xiàn)一個(gè)流,多個(gè)訂閱者的需求時(shí)(這在開(kāi)發(fā)中是很常見(jiàn)的),就需要熱流了
從命名上也很容易理解,SharedFlow即共享的Flow,可以實(shí)現(xiàn)一對(duì)多關(guān)系,SharedFlow是一種熱流

3.2 SharedFlow的使用

我們來(lái)看看SharedFlow的構(gòu)造函數(shù)

public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T>

其主要有3個(gè)參數(shù)
1.replay表示當(dāng)新的訂閱者Collect時(shí),發(fā)送幾個(gè)已經(jīng)發(fā)送過(guò)的數(shù)據(jù)給它,默認(rèn)為0,即默認(rèn)新訂閱者不會(huì)獲取以前的數(shù)據(jù)
2.extraBufferCapacity表示減去replay,MutableSharedFlow還緩存多少數(shù)據(jù),默認(rèn)為0
3.onBufferOverflow表示緩存策略,即緩沖區(qū)滿(mǎn)了之后Flow如何處理,默認(rèn)為掛起
簡(jiǎn)單使用如下

//ViewModel
val sharedFlow=MutableSharedFlow<String>()

viewModelScope.launch{
      sharedFlow.emit("Hello")
      sharedFlow.emit("SharedFlow")
}

//Activity
lifecycleScope.launch{
    viewMode.sharedFlow.collect { 
       print(it)
    }
}

3.3 將冷流轉(zhuǎn)化為SharedFlow

普通flow可使用shareIn擴(kuò)展方法,轉(zhuǎn)化成SharedFlow

    val sharedFlow by lazy {
        flow<Int> {
        //...
        }.shareIn(viewModelScope, WhileSubscribed(500), 0)
    }

shareIn主要也有三個(gè)參數(shù):

@param scope 共享開(kāi)始時(shí)所在的協(xié)程作用域范圍
@param started 控制共享的開(kāi)始和結(jié)束的策略
@param replay 狀態(tài)流的重播個(gè)數(shù)

started 接受以下的三個(gè)值:

  1. Lazily: 當(dāng)首個(gè)訂閱者出現(xiàn)時(shí)開(kāi)始,在scope指定的作用域被結(jié)束時(shí)終止。
  2. Eagerly: 立即開(kāi)始,而在scope指定的作用域被結(jié)束時(shí)終止。
  3. WhileSubscribed: 這種情況有些復(fù)雜,后面會(huì)詳細(xì)講解

對(duì)于那些只執(zhí)行一次的操作,您可以使用Lazily或者Eagerly。然而,如果您需要觀察其他的流,就應(yīng)該使用WhileSubscribed來(lái)實(shí)現(xiàn)細(xì)微但又重要的優(yōu)化工作

3.4 Whilesubscribed策略

WhileSubscribed策略會(huì)在沒(méi)有收集器的情況下取消上游數(shù)據(jù)流,通過(guò)shareIn運(yùn)算符創(chuàng)建的SharedFlow會(huì)把數(shù)據(jù)暴露給視圖 (View),同時(shí)也會(huì)觀察來(lái)自其他層級(jí)或者是上游應(yīng)用的數(shù)據(jù)流。
讓這些流持續(xù)活躍可能會(huì)引起不必要的資源浪費(fèi),例如一直通過(guò)從數(shù)據(jù)庫(kù)連接、硬件傳感器中讀取數(shù)據(jù)等等。當(dāng)您的應(yīng)用轉(zhuǎn)而在后臺(tái)運(yùn)行時(shí),您應(yīng)當(dāng)保持克制并中止這些協(xié)程。

public fun WhileSubscribed(
   stopTimeoutMillis: Long = 0,
   replayExpirationMillis: Long = Long.MAX_VALUE
)

如上所示,它支持兩個(gè)參數(shù):

  1. stopTimeoutMillis 控制一個(gè)以毫秒為單位的延遲值,指的是最后一個(gè)訂閱者結(jié)束訂閱與停止上游流的時(shí)間差。默認(rèn)值是 0 (立即停止).這個(gè)值非常有用,因?yàn)槟赡懿⒉幌胍驗(yàn)橐晥D有幾秒鐘不再監(jiān)聽(tīng)就結(jié)束上游流。這種情況非常常見(jiàn)——比如當(dāng)用戶(hù)旋轉(zhuǎn)設(shè)備時(shí),原來(lái)的視圖會(huì)先被銷(xiāo)毀,然后數(shù)秒鐘內(nèi)重建。
  2. replayExpirationMillis表示數(shù)據(jù)重播的過(guò)時(shí)時(shí)間,如果用戶(hù)離開(kāi)應(yīng)用太久,此時(shí)您不想讓用戶(hù)看到陳舊的數(shù)據(jù),你可以用到這個(gè)參數(shù)

4. StateFlow介紹

4.1 為什么引入StateFlow

我們前面剛剛看了SharedFlow,為什么又冒出個(gè)StateFlow?
StateFlowSharedFlow 的一個(gè)比較特殊的變種,StateFlowLiveData 是最接近的,因?yàn)?

  1. 它始終是有值的。
  2. 它的值是唯一的。
  3. 它允許被多個(gè)觀察者共用 (因此是共享的數(shù)據(jù)流)。
  4. 它永遠(yuǎn)只會(huì)把最新的值重現(xiàn)給訂閱者,這與活躍觀察者的數(shù)量是無(wú)關(guān)的。

可以看出,StateFlowLiveData是比較接近的,可以獲取當(dāng)前的值,可以想像之所以引入StateFlow就是為了替換LiveData

  1. StateFlow繼承于SharedFlow,是SharedFlow的一個(gè)特殊變種
  2. StateFlowLiveData比較相近,相信之所以推出就是為了替換LiveData

4.2 StateFlow的簡(jiǎn)單使用

我們先來(lái)看看構(gòu)造函數(shù):

public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
  1. StateFlow構(gòu)造函數(shù)較為簡(jiǎn)單,只需要傳入一個(gè)默認(rèn)值
  2. StateFlow本質(zhì)上是一個(gè)replay為1,并且沒(méi)有緩沖區(qū)的SharedFlow,因此第一次訂閱時(shí)會(huì)先獲得默認(rèn)值
  3. StateFlow僅在值已更新,并且值發(fā)生了變化時(shí)才會(huì)返回,即如果更新后的值沒(méi)有變化,也沒(méi)會(huì)回調(diào)Collect方法,這點(diǎn)與LiveData不同

SharedFlow類(lèi)似,我們也可以用stateIn將普通流轉(zhuǎn)化成StateFlow

val result: StateFlow<Result<UiState>> = someFlow
    .stateIn(
        scope = viewModelScope, 
        started = WhileSubscribed(5000), 
        initialValue = Result.Loading
    )

shareIn類(lèi)似,唯一不同的時(shí)需要傳入一個(gè)默認(rèn)值
同時(shí)之所以WhileSubscribed中傳入了5000,是為了實(shí)現(xiàn)等待5秒后仍然沒(méi)有訂閱者存在就終止協(xié)程的功能,這個(gè)方法有以下功能

  • 用戶(hù)將您的應(yīng)用轉(zhuǎn)至后臺(tái)運(yùn)行,5 秒鐘后所有來(lái)自其他層的數(shù)據(jù)更新會(huì)停止,這樣可以節(jié)省電量。
  • 最新的數(shù)據(jù)仍然會(huì)被緩存,所以當(dāng)用戶(hù)切換回應(yīng)用時(shí),視圖立即就可以得到數(shù)據(jù)進(jìn)行渲染。
  • 訂閱將被重啟,新數(shù)據(jù)會(huì)填充進(jìn)來(lái),當(dāng)數(shù)據(jù)可用時(shí)更新視圖。
  • 在屏幕旋轉(zhuǎn)時(shí),因?yàn)橹匦掠嗛喌臅r(shí)間在5s內(nèi),因此上游流不會(huì)中止

4.3 在頁(yè)面中觀察StateFlow

LiveData類(lèi)似,我們也需要經(jīng)常在頁(yè)面中觀察StateFlow
觀察StateFlow需要在協(xié)程中,因此我們需要協(xié)程構(gòu)建器,一般我們會(huì)使用下面幾種

  1. lifecycleScope.launch : 立即啟動(dòng)協(xié)程,并且在本 ActivityFragment 銷(xiāo)毀時(shí)結(jié)束協(xié)程。
  2. LaunchWhenStartedLaunchWhenResumed,它會(huì)在lifecycleOwner進(jìn)入X狀態(tài)之前一直等待,又在離開(kāi)X狀態(tài)時(shí)掛起協(xié)程
    image.png

如上圖所示:
1.使用launch是不安全的,在應(yīng)用在后臺(tái)時(shí)也會(huì)接收數(shù)據(jù)更新,可能會(huì)導(dǎo)致應(yīng)用崩潰
2.使用launchWhenStartedlaunchWhenResumed會(huì)好一些,在后臺(tái)時(shí)不會(huì)接收數(shù)據(jù)更新,但是,上游數(shù)據(jù)流會(huì)在應(yīng)用后臺(tái)運(yùn)行期間保持活躍,因此可能浪費(fèi)一定的資源

這么說(shuō)來(lái),我們使用WhileSubscribed進(jìn)行的配置豈不是無(wú)效了嗎?訂閱者一直存在,只有頁(yè)面關(guān)閉時(shí)才會(huì)取消訂閱
官方推薦repeatOnLifecycle來(lái)構(gòu)建協(xié)程
在某個(gè)特定的狀態(tài)滿(mǎn)足時(shí)啟動(dòng)協(xié)程,并且在生命周期所有者退出該狀態(tài)時(shí)停止協(xié)程,如下圖所示。

image.png

比如在某個(gè)Fragment的代碼中:

onCreateView(...) {
    viewLifecycleOwner.lifecycleScope.launch {
        viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {
            myViewModel.myUiState.collect { ... }
        }
    }
}

當(dāng)這個(gè)Fragment處于STARTED狀態(tài)時(shí)會(huì)開(kāi)始收集流,并且在RESUMED狀態(tài)時(shí)保持收集,最終在Fragment進(jìn)入STOPPED狀態(tài)時(shí)結(jié)束收集過(guò)程。
結(jié)合使用repeatOnLifecycle APIWhileSubscribed,可以幫助您的應(yīng)用妥善利用設(shè)備資源的同時(shí),發(fā)揮最佳性能

4.4 頁(yè)面中觀察Flow的最佳方式

通過(guò)ViewModel暴露數(shù)據(jù),并在頁(yè)面中獲取的最佳方式是:

  • ?? 使用帶超時(shí)參數(shù)的 WhileSubscribed 策略暴露 Flow。示例 1
  • ?? 使用 repeatOnLifecycle 來(lái)收集數(shù)據(jù)更新。示例 2
    image.png

最佳實(shí)踐如上圖所示,如果采用其他方式,上游數(shù)據(jù)流會(huì)被一直保持活躍,導(dǎo)致資源浪費(fèi)
當(dāng)然,如果您并不需要使用到Kotlin Flow的強(qiáng)大功能,就用LiveData好了 :)

5 StateFlow與SharedFlow有什么區(qū)別?

從上文其實(shí)可以看出,StateFlowSharedFlow其實(shí)是挺像的,讓人有些傻傻分不清,有時(shí)候也挺難選擇該用哪個(gè)的
我們總結(jié)一下,它們的區(qū)別如下:

  1. SharedFlow配置更為靈活,支持配置replay,緩沖區(qū)大小等,StateFlowSharedFlow的特化版本,replay固定為1,緩沖區(qū)大小默認(rèn)為0
  2. StateFlowLiveData類(lèi)似,支持通過(guò)myFlow.value獲取當(dāng)前狀態(tài),如果有這個(gè)需求,必須使用StateFlow
  3. SharedFlow支持發(fā)出和收集重復(fù)值,而StateFlow當(dāng)value重復(fù)時(shí),不會(huì)回調(diào)collect
    對(duì)于新的訂閱者,StateFlow只會(huì)重播當(dāng)前最新值,SharedFlow可配置重播元素個(gè)數(shù)(默認(rèn)為0,即不重播)

可以看出,StateFlow為我們做了一些默認(rèn)的配置,在SharedFlow上添加了一些默認(rèn)約束,這些配置可能并不符合我們的要求

  1. 它忽略重復(fù)的值,并且是不可配置的。這會(huì)帶來(lái)一些問(wèn)題,比如當(dāng)往List中添加元素并更新時(shí),StateFlow會(huì)認(rèn)為是重復(fù)的值并忽略
  2. 它需要一個(gè)初始值,并且在開(kāi)始訂閱時(shí)會(huì)回調(diào)初始值,這有可能不是我們想要的
  3. 它默認(rèn)是粘性的,新用戶(hù)訂閱會(huì)獲得當(dāng)前的最新值,而且是不可配置的,而SharedFlow可以修改replay

StateFlow施加在SharedFlow上的約束可能不是最適合您,如果不需要訪(fǎng)問(wèn)myFlow.value,并且享受SharedFlow的靈活性,可以選擇考慮使用SharedFlow

總結(jié)

簡(jiǎn)單往往意味著不夠強(qiáng)大,而強(qiáng)大又常常意味著復(fù)雜,兩者往往不能兼得,軟件開(kāi)發(fā)過(guò)程中常常面臨這種取舍。
LiveData的簡(jiǎn)單并不是它的缺點(diǎn),而是它的特點(diǎn)。StateFlowSharedFlow更加強(qiáng)大,但是學(xué)習(xí)成本也顯著的更高.
我們應(yīng)該根據(jù)自己的需求合理選擇組件的使用

  1. 如果你的數(shù)據(jù)流比較簡(jiǎn)單,不需要進(jìn)行線(xiàn)程切換與復(fù)雜的數(shù)據(jù)變換,LiveData對(duì)你來(lái)說(shuō)相信已經(jīng)足夠了
  2. 如果你的數(shù)據(jù)流比較復(fù)雜,需要切換線(xiàn)程等操作,不需要發(fā)送重復(fù)值,需要獲取myFlow.valueStateFlow對(duì)你來(lái)說(shuō)是個(gè)好的選擇
  3. 如果你的數(shù)據(jù)流比較復(fù)雜,同時(shí)不需要獲取myFlow.value,需要配置新用戶(hù)訂閱重播無(wú)素的個(gè)數(shù),或者需要發(fā)送重復(fù)的值,可以考慮使用SharedFlow

參考資料

Google 推薦在 MVVM 架構(gòu)中使用 Kotlin Flow
Migrate from LiveData to StateFlow and SharedFlow
從 LiveData 遷移到 Kotlin 數(shù)據(jù)流
關(guān)于kotlin中的Collections、Sequence、Channel和Flow (二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容