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

1. LiveData有什么不足?
1.1 為什么引入LiveData?
要了解LiveData的不足,我們先了解下LiveData為什么被引入
LiveData 的歷史要追溯到 2017 年。彼時(shí),觀察者模式有效簡(jiǎn)化了開發(fā),但諸如 RxJava 一類的庫對(duì)新手而言有些太過復(fù)雜。為此,架構(gòu)組件團(tuán)隊(duì)打造了 LiveData: 一個(gè)專用于 Android 的具備自主生命周期感知能力的可觀察的數(shù)據(jù)存儲(chǔ)器類。 LiveData被有意簡(jiǎn)化設(shè)計(jì),這使得開發(fā)者很容易上手;而對(duì)于較為復(fù)雜的交互數(shù)據(jù)流場(chǎng)景,建議您使用 RxJava,這樣兩者結(jié)合的優(yōu)勢(shì)就發(fā)揮出來了
可以看出,LiveData就是一個(gè)簡(jiǎn)單易用的,具備感知生命周期能力的觀察者模式
它使用起來非常簡(jiǎn)單,這是它的優(yōu)點(diǎn),也是它的不足,因?yàn)樗鎸?duì)比較復(fù)雜的交互數(shù)據(jù)流場(chǎng)景時(shí),處理起來比較麻煩。
1.2 LiveData的不足
我們上文說過LiveData結(jié)構(gòu)簡(jiǎn)單,但是不夠強(qiáng)大,它有以下不足
1.LiveData只能在主線程更新數(shù)據(jù) 2.LiveData的操作符不夠強(qiáng)大,在處理復(fù)雜數(shù)據(jù)流時(shí)有些捉襟見肘
關(guān)于LiveData只能在主線程更新數(shù)據(jù),有的同學(xué)可能要問,不是有postValue嗎?其實(shí)postValue也是需要切換到到主線程的,如下圖所示:

這意味著當(dāng)我們想要更新LiveData對(duì)象時(shí),我們會(huì)經(jīng)常更改線程(工作線程→主線程),如果在修改LiveData后又要切換回到工作線程那就更麻煩了,同時(shí)postValue可能會(huì)有丟數(shù)據(jù)的問題。
2. Flow介紹
Flow 就是 Kotlin 協(xié)程與響應(yīng)式編程模型結(jié)合的產(chǎn)物,你會(huì)發(fā)現(xiàn)它與 RxJava 非常像,二者之間也有相互轉(zhuǎn)換的 API,使用起來非常方便。
2.1 為什么引入Flow
為什么引入Flow,我們可以從Flow解決了什么問題的角度切入
LiveData不支持線程切換,所有數(shù)據(jù)轉(zhuǎn)換都將在主線程上完成,有時(shí)需要頻繁更改線程,面對(duì)復(fù)雜數(shù)據(jù)流時(shí)處理起來比較麻煩。
而RxJava又有些過于麻煩了,有許多讓人傻傻分不清的操作符,入門門檻較高,同時(shí)需要自己處理生命周期,在生命周期結(jié)束時(shí)取消訂閱
可以看出,Flow是介于LiveData與RxJava之間的一個(gè)解決方案,它有以下特點(diǎn)
Flow 支持線程切換、背壓 Flow 入門的門檻很低,沒有那么多傻傻分不清楚的操作符 簡(jiǎn)單的數(shù)據(jù)轉(zhuǎn)換與操作符,如 map 等等 冷數(shù)據(jù)流,不消費(fèi)則不生產(chǎn)數(shù)據(jù),這一點(diǎn)與LiveData不同:LiveData的發(fā)送端并不依賴于接收端。屬于kotlin協(xié)程的一部分,可以很好的與協(xié)程基礎(chǔ)設(shè)施結(jié)合
關(guān)于Flow的使用,比較簡(jiǎn)單,有興趣的同學(xué)可參閱文檔:Flow文檔
3. SharedFlow介紹
我們上面介紹過,Flow 是冷流,什么是冷流?
冷流 :只有訂閱者訂閱時(shí),才開始執(zhí)行發(fā)射數(shù)據(jù)流的代碼。并且冷流和訂閱者只能是一對(duì)一的關(guān)系,當(dāng)有多個(gè)不同的訂閱者時(shí),消息是重新完整發(fā)送的。也就是說對(duì)冷流而言,有多個(gè)訂閱者的時(shí)候,他們各自的事件是獨(dú)立的。
熱流:無論有沒有訂閱者訂閱,事件始終都會(huì)發(fā)生。當(dāng) 熱流有多個(gè)訂閱者時(shí),熱流與訂閱者們的關(guān)系是一對(duì)多的關(guān)系,可以與多個(gè)訂閱者共享信息。
3.1 為什么引入SharedFlow
上面其實(shí)已經(jīng)說得很清楚了,冷流和訂閱者只能是一對(duì)一的關(guān)系,當(dāng)我們要實(shí)現(xiàn)一個(gè)流,多個(gè)訂閱者的需求時(shí)(這在開發(fā)中是很常見的),就需要熱流了
從命名上也很容易理解,SharedFlow即共享的Flow,可以實(shí)現(xiàn)一對(duì)多關(guān)系,SharedFlow是一種熱流
3.2 SharedFlow的使用
我們來看看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ā)送過的數(shù)據(jù)給它,默認(rèn)為0,即默認(rèn)新訂閱者不會(huì)獲取以前的數(shù)據(jù)2. extraBufferCapacity表示減去replay,MutableSharedFlow還緩存多少數(shù)據(jù),默認(rèn)為03. onBufferOverflow表示緩存策略,即緩沖區(qū)滿了之后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共享開始時(shí)所在的協(xié)程作用域范圍
@param started控制共享的開始和結(jié)束的策略
@param replay狀態(tài)流的重播個(gè)數(shù)
started 接受以下的三個(gè)值:
1. Lazily:當(dāng)首個(gè)訂閱者出現(xiàn)時(shí)開始,在scope指定的作用域被結(jié)束時(shí)終止。2. Eagerly:立即開始,而在scope指定的作用域被結(jié)束時(shí)終止。3. WhileSubscribed:這種情況有些復(fù)雜,后面會(huì)詳細(xì)講解
對(duì)于那些只執(zhí)行一次的操作,您可以使用Lazily或者Eagerly。然而,如果您需要觀察其他的流,就應(yīng)該使用WhileSubscribed來實(shí)現(xiàn)細(xì)微但又重要的優(yōu)化工作
3.4 Whilesubscribed策略
WhileSubscribed策略會(huì)在沒有收集器的情況下取消上游數(shù)據(jù)流,通過shareIn運(yùn)算符創(chuàng)建的SharedFlow會(huì)把數(shù)據(jù)暴露給視圖 (View),同時(shí)也會(huì)觀察來自其他層級(jí)或者是上游應(yīng)用的數(shù)據(jù)流。
讓這些流持續(xù)活躍可能會(huì)引起不必要的資源浪費(fèi),例如一直通過從數(shù)據(jù)庫連接、硬件傳感器中讀取數(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)聽就結(jié)束上游流。這種情況非常常見——比如當(dāng)用戶旋轉(zhuǎn)設(shè)備時(shí),原來的視圖會(huì)先被銷毀,然后數(shù)秒鐘內(nèi)重建。2. replayExpirationMillis表示數(shù)據(jù)重播的過時(shí)時(shí)間,如果用戶離開應(yīng)用太久,此時(shí)您不想讓用戶看到陳舊的數(shù)據(jù),你可以用到這個(gè)參數(shù)
4. StateFlow介紹
4.1 為什么引入StateFlow
我們前面剛剛看了SharedFlow,為什么又冒出個(gè)StateFlow?
StateFlow 是 SharedFlow 的一個(gè)比較特殊的變種,StateFlow 與 LiveData是最接近的,因?yàn)?
1.它始終是有值的。 2.它的值是唯一的。 3.它允許被多個(gè)觀察者共用 (因此是共享的數(shù)據(jù)流)。 4.它永遠(yuǎn)只會(huì)把最新的值重現(xiàn)給訂閱者,這與活躍觀察者的數(shù)量是無關(guān)的。
可以看出,StateFlow與LiveData是比較接近的,可以獲取當(dāng)前的值,可以想像之所以引入StateFlow就是為了替換LiveData
總結(jié)如下:
1. StateFlow繼承于SharedFlow,是SharedFlow的一個(gè)特殊變種2. StateFlow與LiveData比較相近,相信之所以推出就是為了替換LiveData
4.2 StateFlow的簡(jiǎn)單使用
我們先來看看構(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,并且沒有緩沖區(qū)的SharedFlow,因此第一次訂閱時(shí)會(huì)先獲得默認(rèn)值 3.StateFlow僅在值已更新,并且值發(fā)生了變化時(shí)才會(huì)返回,即如果更新后的值沒有變化,也沒會(huì)回調(diào)Collect方法,這點(diǎn)與LiveData不同
與SharedFlow類似,我們也可以用stateIn將普通流轉(zhuǎn)化成StateFlow
val result: StateFlow<Result<UiState>> = someFlow
.stateIn(
scope = viewModelScope,
started = WhileSubscribed(5000),
initialValue = Result.Loading
)
與shareIn類似,唯一不同的時(shí)需要傳入一個(gè)默認(rèn)值
同時(shí)之所以WhileSubscribed中傳入了5000,是為了實(shí)現(xiàn)等待5秒后仍然沒有訂閱者存在就終止協(xié)程的功能,這個(gè)方法有以下功能
用戶將您的應(yīng)用轉(zhuǎn)至后臺(tái)運(yùn)行,5 秒鐘后所有來自其他層的數(shù)據(jù)更新會(huì)停止,這樣可以節(jié)省電量。最新的數(shù)據(jù)仍然會(huì)被緩存,所以當(dāng)用戶切換回應(yīng)用時(shí),視圖立即就可以得到數(shù)據(jù)進(jìn)行渲染。訂閱將被重啟,新數(shù)據(jù)會(huì)填充進(jìn)來,當(dāng)數(shù)據(jù)可用時(shí)更新視圖。
在屏幕旋轉(zhuǎn)時(shí),因?yàn)橹匦掠嗛喌臅r(shí)間在5s內(nèi),因此上游流不會(huì)中止
4.3 在頁面中觀察StateFlow
與LiveData類似,我們也需要經(jīng)常在頁面中觀察StateFlow
觀察StateFlow需要在協(xié)程中,因此我們需要協(xié)程構(gòu)建器,一般我們會(huì)使用下面幾種
lifecycleScope.launch: 立即啟動(dòng)協(xié)程,并且在本 Activity或Fragment 銷毀時(shí)結(jié)束協(xié)程。LaunchWhenStarted和LaunchWhenResumed,它會(huì)在lifecycleOwner進(jìn)入X狀態(tài)之前一直等待,又在離開X狀態(tài)時(shí)掛起協(xié)程

如上圖所示:
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)一定的資源
這么說來,我們使用WhileSubscribed進(jìn)行的配置豈不是無效了嗎?訂閱者一直存在,只有頁面關(guān)閉時(shí)才會(huì)取消訂閱
官方推薦repeatOnLifecycle來構(gòu)建協(xié)程
在某個(gè)特定的狀態(tài)滿足時(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ì)開始收集流,并且在RESUMED狀態(tài)時(shí)保持收集,最終在Fragment進(jìn)入STOPPED狀態(tài)時(shí)結(jié)束收集過程。
結(jié)合使用repeatOnLifecycle API和WhileSubscribed,可以幫助您的應(yīng)用妥善利用設(shè)備資源的同時(shí),發(fā)揮最佳性能
4.4 頁面中觀察Flow的最佳方式
通過ViewModel暴露數(shù)據(jù),并在頁面中獲取的最佳方式是:
?? 使用帶超時(shí)參數(shù)的 WhileSubscribed策略暴露 Flow。示例 1?? 使用 repeatOnLifecycle來收集數(shù)據(jù)更新。示例 2

最佳實(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)為0StateFlow與LiveData類似,支持通過myFlow.value獲取當(dāng)前狀態(tài),如果有這個(gè)需求,必須使用StateFlowSharedFlow支持發(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ì)帶來一些問題,比如當(dāng)往List中添加元素并更新時(shí),
StateFlow會(huì)認(rèn)為是重復(fù)的值并忽略它需要一個(gè)初始值,并且在開始訂閱時(shí)會(huì)回調(diào)初始值,這有可能不是我們想要的
它默認(rèn)是粘性的,新用戶訂閱會(huì)獲得當(dāng)前的最新值,而且是不可配置的,而
SharedFlow可以修改replay
StateFlow施加在SharedFlow上的約束可能不是最適合您,如果不需要訪問myFlow.value,并且享受SharedFlow的靈活性,可以選擇考慮使用SharedFlow
總結(jié)
簡(jiǎn)單往往意味著不夠強(qiáng)大,而強(qiáng)大又常常意味著復(fù)雜,兩者往往不能兼得,軟件開發(fā)過程中常常面臨這種取舍。
LiveData的簡(jiǎn)單并不是它的缺點(diǎn),而是它的特點(diǎn)。StateFlow與SharedFlow更加強(qiáng)大,但是學(xué)習(xí)成本也顯著的更高。
我們應(yīng)該根據(jù)自己的需求合理選擇組件的使用
如果你的數(shù)據(jù)流比較簡(jiǎn)單,不需要進(jìn)行線程切換與復(fù)雜的數(shù)據(jù)變換,LiveData對(duì)你來說相信已經(jīng)足夠了
如果你的數(shù)據(jù)流比較復(fù)雜,需要切換線程等操作,不需要發(fā)送重復(fù)值,需要獲取myFlow.value,StateFlow對(duì)你來說是個(gè)好的選擇
如果你的數(shù)據(jù)流比較復(fù)雜,同時(shí)不需要獲取myFlow.value,需要配置新用戶訂閱重播無素的個(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 (二)
·················END················· 推薦閱讀
? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!
BATcoder技術(shù)群,讓一部分人先進(jìn)大廠
大家好,我是劉望舒,騰訊TVP,著有三本業(yè)內(nèi)知名暢銷書,連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,谷歌開發(fā)者社區(qū)特邀講師,百度百科收錄的高級(jí)技術(shù)專家。
前華為技術(shù)專家,現(xiàn)大廠技術(shù)負(fù)責(zé)人。
想要加入 BATcoder技術(shù)群,公號(hào)回復(fù)
BAT即可。為了防止失聯(lián),歡迎關(guān)注我的小號(hào)
微信改了推送機(jī)制,真愛請(qǐng)星標(biāo)本公號(hào)??
