<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          谷歌推薦 Flow 取代 LiveData,真香?

          共 8360字,需瀏覽 17分鐘

           ·

          2021-08-28 00:18


          作者: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)為0
          • 3.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?

          StateFlowSharedFlow 的一個(gè)比較特殊的變種,StateFlowLiveData是最接近的,因?yàn)?

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

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

          總結(jié)如下:

          • 1.StateFlow繼承于SharedFlow,是SharedFlow的一個(gè)特殊變種
          • 2.StateFlowLiveData比較相近,相信之所以推出就是為了替換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é)程。
          • LaunchWhenStartedLaunchWhenResumed,它會(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.使用launchWhenStartedlaunchWhenResumed會(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í)可以看出,StateFlowSharedFlow其實(shí)是挺像的,讓人有些傻傻分不清,有時(shí)候也挺難選擇該用哪個(gè)的 我們總結(jié)一下,它們的區(qū)別如下:

          • SharedFlow配置更為靈活,支持配置replay,緩沖區(qū)大小等,StateFlowSharedFlow的特化版本,replay固定為1,緩沖區(qū)大小默認(rèn)為0

          • StateFlowLiveData類似,支持通過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ì)帶來一些問題,比如當(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』做了多年安卓還沒編譯過源碼?一個(gè)視頻帶你玩轉(zhuǎn)!

          ? 『BATcoder』我去!安裝Ubuntu還有坑?

          ? 重生!進(jìn)階三部曲第一部《Android進(jìn)階之光》第2版 出版!

          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)??
          瀏覽 113
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  羞羞草视频 | 伊人大香蕉电影 | 日本特黄一级 | 家庭乱伦_第1页_桃花影视 | 男女黄A片|