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

1. LiveData有什么不足?
1.1 為什么引入LiveData?
要了解LiveData的不足,我們先了解下LiveData為什么被引入
LiveData的歷史要追溯到 2017 年,彼時,觀察者模式有效簡化了開發,但諸如RxJava一類的庫對新手而言有些太過復雜,為此,架構組件團隊打造了LiveData: 一個專用于Android的具備自主生命周期感知能力的可觀察的資料存盤器類,LiveData被有意簡化設計,這使得開發者很容易上手;而對于較為復雜的互動資料流場景,建議您使用RxJava,這樣兩者結合的優勢就發揮出來了
可以看出,LiveData就是一個簡單易用的,具備感知生命周期能力的觀察者模式
它使用起來非常簡單,這是它的優點,也是它的不足,因為它面對比較復雜的互動資料流場景時,處理起來比較麻煩
1.2 LiveData的不足
我們上文說過LiveData結構簡單,但是不夠強大,它有以下不足
1.LiveData只能在主執行緒更新資料
2.LiveData的運算子不夠強大,在處理復雜資料流時有些捉襟見肘
關于LiveData只能在主執行緒更新資料,有的同學可能要問,不是有postValue嗎?其實postValue也是需要切換到到主執行緒的,如下圖所示:

這意味著當我們想要更新LiveData物件時,我們會經常更改執行緒(作業執行緒→主執行緒),如果在修改LiveData后又要切換回到作業執行緒那就更麻煩了,同時postValue可能會有丟資料的問題,
2. Flow介紹
Flow 就是 Kotlin 協程與回應式編程模型結合的產物,你會發現它與 RxJava 非常像,二者之間也有相互轉換的 API,使用起來非常方便,
2.1 為什么引入Flow
為什么引入Flow,我們可以從Flow解決了什么問題的角度切入
LiveData不支持執行緒切換,所有資料轉換都將在主執行緒上完成,有時需要頻繁更改執行緒,面對復雜資料流時處理起來比較麻煩- 而
RxJava又有些過于麻煩了,有許多讓人傻傻分不清的運算子,入門門檻較高,同時需要自己處理生命周期,在生命周期結束時取消訂閱
可以看出,Flow是介于LiveData與RxJava之間的一個解決方案,它有以下特點
Flow支持執行緒切換、背壓Flow入門的門檻很低,沒有那么多傻傻分不清楚的運算子- 簡單的資料轉換與運算子,如
map等等 - 冷資料流,不消費則不生產資料,這一點與
LiveData不同:LiveData的發送端并不依賴于接收端, - 屬于
kotlin協程的一部分,可以很好的與協程基礎設施結合
關于Flow的使用,比較簡單,有興趣的同學可參閱檔案:Flow檔案
3. SharedFlow介紹
我們上面介紹過,Flow 是冷流,什么是冷流?
- 冷流 :只有
訂閱者訂閱時,才開始執行發射資料流的代碼,并且冷流和訂閱者只能是一對一的關系,當有多個不同的訂閱者時,訊息是重新完整發送的,也就是說對冷流而言,有多個訂閱者的時候,他們各自的事件是獨立的, - 熱流:無論有沒有
訂閱者訂閱,事件始終都會發生,當熱流有多個訂閱者時,熱流與訂閱者們的關系是一對多的關系,可以與多個訂閱者共享資訊,
3.1 為什么引入SharedFlow
上面其實已經說得很清楚了,冷流和訂閱者只能是一對一的關系,當我們要實作一個流,多個訂閱者的需求時(這在開發中是很常見的),就需要熱流了
從命名上也很容易理解,SharedFlow即共享的Flow,可以實作一對多關系,SharedFlow是一種熱流
3.2 SharedFlow的使用
我們來看看SharedFlow的建構式
public fun <T> MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T>
復制代碼
其主要有3個引數
1.replay表示當新的訂閱者Collect時,發送幾個已經發送過的資料給它,默認為0,即默認新訂閱者不會獲取以前的資料
2.extraBufferCapacity表示減去replay,MutableSharedFlow還快取多少資料,默認為0
3.onBufferOverflow表示快取策略,即緩沖區滿了之后Flow如何處理,默認為掛起
簡單使用如下:
//ViewModel
val sharedFlow=MutableSharedFlow<String>()
viewModelScope.launch{
sharedFlow.emit("Hello")
sharedFlow.emit("SharedFlow")
}
//Activity
lifecycleScope.launch{
viewMode.sharedFlow.collect {
print(it)
}
}
復制代碼
3.3 將冷流轉化為SharedFlow
普通flow可使用shareIn擴展方法,轉化成SharedFlow
val sharedFlow by lazy {
flow<Int> {
//...
}.shareIn(viewModelScope, WhileSubscribed(500), 0)
}
復制代碼
shareIn主要也有三個引數:
@param
scope共享開始時所在的協程作用域范圍
@paramstarted控制共享的開始和結束的策略
@paramreplay狀態流的重播個數
started 接受以下的三個值:
1.Lazily: 當首個訂閱者出現時開始,在scope指定的作用域被結束時終止,
2.Eagerly: 立即開始,而在scope指定的作用域被結束時終止,
3.WhileSubscribed: 這種情況有些復雜,后面會詳細講解
對于那些只執行一次的操作,您可以使用Lazily或者Eagerly,然而,如果您需要觀察其他的流,就應該使用WhileSubscribed來實作細微但又重要的優化作業
3.4 Whilesubscribed策略
WhileSubscribed策略會在沒有收集器的情況下取消上游資料流,通過shareIn運算子創建的SharedFlow會把資料暴露給視圖 (View),同時也會觀察來自其他層級或者是上游應用的資料流,
讓這些流持續活躍可能會引起不必要的資源浪費,例如一直通過從資料庫連接、硬體傳感器中讀取資料等等,當您的應用轉而在后臺運行時,您應當保持克制并中止這些協程,
public fun WhileSubscribed(
stopTimeoutMillis: Long = 0,
replayExpirationMillis: Long = Long.MAX_VALUE
)
復制代碼
如上所示,它支持兩個引數:
- 1.
stopTimeoutMillis控制一個以毫秒為單位的延遲值,指的是最后一個訂閱者結束訂閱與停止上游流的時間差,默認值是 0 (立即停止).這個值非常有用,因為您可能并不想因為視圖有幾秒鐘不再監聽就結束上游流,這種情況非常常見——比如當用戶旋轉設備時,原來的視圖會先被銷毀,然后數秒鐘內重建, - 2.
replayExpirationMillis表示資料重播的過時時間,如果用戶離開應用太久,此時您不想讓用戶看到陳舊的資料,你可以用到這個引數
4. StateFlow介紹
4.1 為什么引入StateFlow
我們前面剛剛看了SharedFlow,為什么又冒出個StateFlow?
StateFlow 是 SharedFlow 的一個比較特殊的變種,StateFlow 與 LiveData 是最接近的,因為:
- 1.它始終是有值的,
- 2.它的值是唯一的,
- 3.它允許被多個觀察者共用 (因此是共享的資料流),
- 4.它永遠只會把最新的值重現給訂閱者,這與活躍觀察者的數量是無關的,
可以看出,StateFlow與LiveData是比較接近的,可以獲取當前的值,可以想像之所以引入StateFlow就是為了替換LiveData
總結如下:
1.StateFlow繼承于SharedFlow,是SharedFlow的一個特殊變種
2.StateFlow與LiveData比較相近,相信之所以推出就是為了替換LiveData
4.2 StateFlow的簡單使用
我們先來看看建構式:
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
復制代碼
1.StateFlow建構式較為簡單,只需要傳入一個默認值
2.StateFlow本質上是一個replay為1,并且沒有緩沖區的SharedFlow,因此第一次訂閱時會先獲得默認值
3.StateFlow僅在值已更新,并且值發生了變化時才會回傳,即如果更新后的值沒有變化,也沒會回呼Collect方法,這點與LiveData不同
與StateFlow類似,我們也可以用stateIn將普通流轉化成SharedFlow
val result: StateFlow<Result<UiState>> = someFlow
.stateIn(
scope = viewModelScope,
started = WhileSubscribed(5000),
initialValue = Result.Loading
)
復制代碼
與shareIn類似,唯一不同的時需要傳入一個默認值
同時之所以WhileSubscribed中傳入了5000,是為了實作等待5秒后仍然沒有訂閱者存在就終止協程的功能,這個方法有以下功能
- 用戶將您的應用轉至后臺運行,5 秒鐘后所有來自其他層的資料更新會停止,這樣可以節省電量,
- 最新的資料仍然會被快取,所以當用戶切換回應用時,視圖立即就可以得到資料進行渲染,
- 訂閱將被重啟,新資料會填充進來,當資料可用時更新視圖,
- 在螢屏旋轉時,因為重新訂閱的時間在5s內,因此上游流不會中止
4.3 在頁面中觀察StateFlow
與LiveData類似,我們也需要經常在頁面中觀察StateFlow
觀察StateFlow需要在協程中,因此我們需要協程構建器,一般我們會使用下面幾種
lifecycleScope.launch: 立即啟動協程,并且在本Activity或Fragment銷毀時結束協程,LaunchWhenStarted和LaunchWhenResumed,它會在lifecycleOwner進入X狀態之前一直等待,又在離開X狀態時掛起協程

如上圖所示:
1.使用launch是不安全的,在應用在后臺時也會接收資料更新,可能會導致應用崩潰
2.使用launchWhenStarted或launchWhenResumed會好一些,在后臺時不會接收資料更新,但是,上游資料流會在應用后臺運行期間保持活躍,因此可能浪費一定的資源
這么說來,我們使用WhileSubscribed進行的配置豈不是無效了嗎?訂閱者一直存在,只有頁面關閉時才會取消訂閱
官方推薦repeatOnLifecycle來構建協程
在某個特定的狀態滿足時啟動協程,并且在生命周期所有者退出該狀態時停止協程,如下圖所示,

比如在某個Fragment的代碼中:
onCreateView(...) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {
myViewModel.myUiState.collect { ... }
}
}
}
復制代碼
當這個Fragment處于STARTED狀態時會開始收集流,并且在RESUMED狀態時保持收集,最終在Fragment進入STOPPED狀態時結束收集程序,
結合使用repeatOnLifecycle API和WhileSubscribed,可以幫助您的應用妥善利用設備資源的同時,發揮最佳性能
4.4 頁面中觀察Flow的最佳方式
通過ViewModel暴露資料,并在頁面中獲取的最佳方式是:
- ?? 使用帶超時引數的
WhileSubscribed策略暴露Flow,示例 1 - ?? 使用
repeatOnLifecycle來收集資料更新,示例 2

最佳實踐如上圖所示,如果采用其他方式,上游資料流會被一直保持活躍,導致資源浪費
當然,如果您并不需要使用到Kotlin Flow的強大功能,就用LiveData好了 😃
5 StateFlow與SharedFlow有什么區別?
從上文其實可以看出,StateFlow與SharedFlow其實是挺像的,讓人有些傻傻分不清,有時候也挺難選擇該用哪個的
我們總結一下,它們的區別如下:
SharedFlow配置更為靈活,支持配置replay,緩沖區大小等,StateFlow是SharedFlow的特化版本,replay固定為1,緩沖區大小默認為0StateFlow與LiveData類似,支持通過myFlow.value獲取當前狀態,如果有這個需求,必須使用StateFlowSharedFlow支持發出和收集重復值,而StateFlow當value重復時,不會回呼collect- 對于新的訂閱者,
StateFlow只會重播當前最新值,SharedFlow可配置重播元素個數(默認為0,即不重播)
可以看出,StateFlow為我們做了一些默認的配置,在SharedFlow上添加了一些默認約束,這些配置可能并不符合我們的要求
- 它忽略重復的值,并且是不可配置的,這會帶來一些問題,比如當往
List中添加元素并更新時,StateFlow會認為是重復的值并忽略 - 它需要一個初始值,并且在開始訂閱時會回呼初始值,這有可能不是我們想要的
- 它默認是粘性的,新用戶訂閱會獲得當前的最新值,而且是不可配置的,而
SharedFlow可以修改replay
StateFlow施加在SharedFlow上的約束可能不是最適合您,如果不需要訪問myFlow.value,并且享受SharedFlow的靈活性,可以選擇考慮使用SharedFlow
總結
簡單往往意味著不夠強大,而強大又常常意味著復雜,兩者往往不能兼得,軟體開發程序中常常面臨這種取舍,
LiveData的簡單并不是它的缺點,而是它的特點,StateFlow與SharedFlow更加強大,但是學習成本也顯著的更高.
我們應該根據自己的需求合理選擇組件的使用
- 如果你的資料流比較簡單,不需要進行執行緒切換與復雜的資料變換,
LiveData對你來說相信已經足夠了 - 如果你的資料流比較復雜,需要切換執行緒等操作,不需要發送重復值,需要獲取
myFlow.value,StateFlow對你來說是個好的選擇 - 如果你的資料流比較復雜,同時不需要獲取
myFlow.value,需要配置新用戶訂閱重播無素的個數,或者需要發送重復的值,可以考慮使用SharedFlow
學習推薦
GitHub大佬神仙筆記《Jetpack 高級強化實戰》
有需要的朋友可以直接點擊【此處】或者通過下方代碼塊找我免費獲取全套資料,
// Wechat number(可復制):
study5233
一、初識ConstraintLayout之實作登錄頁面
1.創建專案
2.沉浸式的布局
3.富文本
4.屬性影片

二、Navigation實踐之實作APP主框架以及Navigation的相關介紹
1.搭建 Bottom Navigation Activity
2.導航界面跳轉
3.Navigation傳值
4.Navigation跳轉影片
5.導航檔案拆分
6.Deeplink導航

三、使用 Coroutines, Retrofit, Moshi實作網路資料請求
1.kotlin - Coroutine 協程
2.用協程和Retrofit實作網路請求
3.結語 - 協程值得一學

四、使用 TabLayout,ViewPager2 ,RecyclerView實作實作歌單廣場頁面
1.ViewPager2
2.TabLayout
3.RecyclerView
4.網路資料請求和資料填充
5.優化界面

五、歌單頁面MVVM架構改造及其ViewModel和LiveData的使用介紹
六、Paging實作加載更多和下拉重繪,錯誤后重新請求
七、vlayout嵌套橫向RecyclerView和Banner 實作主頁的展示,自定義Moshi的JsonAdapter
八、Room資料庫實作增刪改查和事務處理
九、Room資料庫Migration
十、ExoPlayer進行視頻播放的實作
十一、MotionLayout讓影片如此簡單
十二、Kotlin Flow基礎知識詳解
十三、Kotlin Flow專案實戰-網路、資料庫和UI的應用
十四、View Binding替代ButterKnife和Kotlin synthetics
……(內容較多,可以自己領取完整版手冊查看)
有需要的朋友可以直接點擊【此處】或者通過下方代碼塊找我免費獲取全套資料,
B站視頻系列
-
【2021Android面試真題決議大合集】騰訊/阿里/百度/位元組/京東……全收錄
-
FrameWork層原始碼分析
-
Android專案實戰
-
Android高級UI實戰
-
性能優化
-
Android 底層原理決議
-
Android零基礎
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/290478.html
標籤:其他
上一篇:Unity 實戰專案 ??| Unity接入 百度語音識別 SDK!一篇文章搞定在Unity中實作語音識別!(萬字完整教程)
