[轉(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.SharedFlow與StateFlow的介紹與它們之間的區(qū)別
本文具體目錄如下所示:

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)大,它有以下不足
-
LiveData只能在主線(xiàn)程更新數(shù)據(jù) -
LiveData的操作符不夠強(qiáng)大,在處理復(fù)雜數(shù)據(jù)流時(shí)有些捉襟見(jiàn)肘
關(guān)于LiveData只能在主線(xiàn)程更新數(shù)據(jù),有的同學(xué)可能要問(wèn),不是有postValue嗎?其實(shí)postValue也是需要切換到到主線(xiàn)程的,如下圖所示:

這意味著當(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)題的角度切入
-
LiveData不支持線(xiàn)程切換,所有數(shù)據(jù)轉(zhuǎn)換都將在主線(xiàn)程上完成,有時(shí)需要頻繁更改線(xiàn)程,面對(duì)復(fù)雜數(shù)據(jù)流時(shí)處理起來(lái)比較麻煩 - 而
RxJava又有些過(guò)于麻煩了,有許多讓人傻傻分不清的操作符,入門(mén)門(mén)檻較高,同時(shí)需要自己處理生命周期,在生命周期結(jié)束時(shí)取消訂閱
可以看出,Flow是介于LiveData與RxJava之間的一個(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é)程作用域范圍
@paramstarted控制共享的開(kāi)始和結(jié)束的策略
@paramreplay狀態(tài)流的重播個(gè)數(shù)
started 接受以下的三個(gè)值:
-
Lazily: 當(dāng)首個(gè)訂閱者出現(xiàn)時(shí)開(kāi)始,在scope指定的作用域被結(jié)束時(shí)終止。 -
Eagerly: 立即開(kāi)始,而在scope指定的作用域被結(jié)束時(shí)終止。 -
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ù):
-
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)重建。 -
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?
StateFlow 是 SharedFlow 的一個(gè)比較特殊的變種,StateFlow與 LiveData 是最接近的,因?yàn)?
- 它始終是有值的。
- 它的值是唯一的。
- 它允許被多個(gè)觀察者共用 (因此是共享的數(shù)據(jù)流)。
- 它永遠(yuǎn)只會(huì)把最新的值重現(xiàn)給訂閱者,這與活躍觀察者的數(shù)量是無(wú)關(guān)的。
可以看出,StateFlow與LiveData是比較接近的,可以獲取當(dāng)前的值,可以想像之所以引入StateFlow就是為了替換LiveData
-
StateFlow繼承于SharedFlow,是SharedFlow的一個(gè)特殊變種 -
StateFlow與LiveData比較相近,相信之所以推出就是為了替換LiveData
4.2 StateFlow的簡(jiǎn)單使用
我們先來(lái)看看構(gòu)造函數(shù):
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
-
StateFlow構(gòu)造函數(shù)較為簡(jiǎn)單,只需要傳入一個(gè)默認(rèn)值 -
StateFlow本質(zhì)上是一個(gè)replay為1,并且沒(méi)有緩沖區(qū)的SharedFlow,因此第一次訂閱時(shí)會(huì)先獲得默認(rèn)值 -
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ì)使用下面幾種
-
lifecycleScope.launch: 立即啟動(dòng)協(xié)程,并且在本Activity或Fragment銷(xiāo)毀時(shí)結(jié)束協(xié)程。 -
LaunchWhenStarted和LaunchWhenResumed,它會(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.使用launchWhenStarted或launchWhenResumed會(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é)程,如下圖所示。

比如在某個(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 API和WhileSubscribed,可以幫助您的應(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í)可以看出,StateFlow與SharedFlow其實(shí)是挺像的,讓人有些傻傻分不清,有時(shí)候也挺難選擇該用哪個(gè)的
我們總結(jié)一下,它們的區(qū)別如下:
-
SharedFlow配置更為靈活,支持配置replay,緩沖區(qū)大小等,StateFlow是SharedFlow的特化版本,replay固定為1,緩沖區(qū)大小默認(rèn)為0 -
StateFlow與LiveData類(lèi)似,支持通過(guò)myFlow.value獲取當(dāng)前狀態(tài),如果有這個(gè)需求,必須使用StateFlow -
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)約束,這些配置可能并不符合我們的要求
- 它忽略重復(fù)的值,并且是不可配置的。這會(huì)帶來(lái)一些問(wèn)題,比如當(dāng)往
List中添加元素并更新時(shí),StateFlow會(huì)認(rèn)為是重復(fù)的值并忽略 - 它需要一個(gè)初始值,并且在開(kāi)始訂閱時(shí)會(huì)回調(diào)初始值,這有可能不是我們想要的
- 它默認(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)。StateFlow與SharedFlow更加強(qiáng)大,但是學(xué)習(xí)成本也顯著的更高.
我們應(yīng)該根據(jù)自己的需求合理選擇組件的使用
- 如果你的數(shù)據(jù)流比較簡(jiǎn)單,不需要進(jìn)行線(xiàn)程切換與復(fù)雜的數(shù)據(jù)變換,
LiveData對(duì)你來(lái)說(shuō)相信已經(jīng)足夠了 - 如果你的數(shù)據(jù)流比較復(fù)雜,需要切換線(xiàn)程等操作,不需要發(fā)送重復(fù)值,需要獲取
myFlow.value,StateFlow對(duì)你來(lái)說(shuō)是個(gè)好的選擇 - 如果你的數(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 (二)

