一文讀懂Android?Kotlin的數(shù)據(jù)流
一、Android分層架構(gòu)
不管是早期的MVC、MVP,還是最新的MVVM和MVI架構(gòu),這些框架一直解決的都是一個(gè)數(shù)據(jù)流的問題。一個(gè)良好的數(shù)據(jù)流框架,每一層的職責(zé)是單一的。例如,我們可以在表現(xiàn)層(Presentation Layer)的基礎(chǔ)上添加一個(gè)領(lǐng)域?qū)樱―omain Layer) 來保存業(yè)務(wù)邏輯,使用數(shù)據(jù)層(Data Layer)對(duì)上層屏蔽數(shù)據(jù)來源(數(shù)據(jù)可能來自遠(yuǎn)程服務(wù),可能是本地?cái)?shù)據(jù)庫)。
在Android中,一個(gè)典型的Android分層架構(gòu)圖如下:
其中,我們需要重點(diǎn)看下Presenter 和 ViewModel, Presenter 和 ViewModel向 View 提供數(shù)據(jù)的機(jī)制是不同的。
- Presenter: Presenter通過持有 View 的引用并直接調(diào)用操作 View,以此向 View 提供和更新數(shù)據(jù)。
- ViewModel:ViewModel 通過將可觀察的數(shù)據(jù)暴露給觀察者來向 View 提供和更新數(shù)據(jù)。
目前,官方提供的可觀察的數(shù)據(jù)組件有LiveData、StateFlow和SharedFlow??赡艽蠹覍?duì)LiveData比較熟悉,配合ViewModel可以很方便的實(shí)現(xiàn)數(shù)據(jù)流的流轉(zhuǎn)。不過,LiveData也有很多常見的缺陷,并且使用場(chǎng)景也比較固定,如果網(wǎng)上出現(xiàn)了KotlinFlow 替代 LiveData的聲音。那么 Flow 真的會(huì)替代 LiveData嗎?Flow 真的適合你的項(xiàng)目嗎?看完下面的分析后,你定會(huì)有所收獲。
二、ViewModel + LiveData
ViewModel的作用是將視圖和邏輯進(jìn)行分離,Activity或者Fragment只負(fù)責(zé)UI顯示部分,網(wǎng)絡(luò)請(qǐng)求或者數(shù)據(jù)庫操作則有ViewModel負(fù)責(zé)。ViewModel旨在以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù),讓數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置更改后繼續(xù)留存。并且ViewModel不持有View層的實(shí)例,通過LiveData與Activity或者Fragment通訊,不需要擔(dān)心潛在的內(nèi)存泄漏問題。
而LiveData 則是一種可觀察的數(shù)據(jù)存儲(chǔ)器類,與常規(guī)的可觀察類不同,LiveData 具有生命周期感知能力,它遵循其他應(yīng)用組件(如 Activity、Fragment 或 Service)的生命周期。這種感知能力可確保LiveData當(dāng)數(shù)據(jù)源發(fā)生變化的時(shí)候,通知它的觀察者更新UI界面。同時(shí)它只會(huì)通知處于Active狀態(tài)的觀察者更新界面,如果某個(gè)觀察者的狀態(tài)處于Paused或Destroyed時(shí)那么它將不會(huì)收到通知,所以不用擔(dān)心內(nèi)存泄漏問題。
下面是官方發(fā)布的架構(gòu)組件庫的生命周期的說明:
2.1 LiveData 特性
通過前面的介紹可以知道,LiveData 是 Android Jetpack Lifecycle 組件中的內(nèi)容,具有生命周期感知能力。一句話概括就是:LiveData 是可感知生命周期的,可觀察的,數(shù)據(jù)持有者。
特點(diǎn)如下:
- 觀察者的回調(diào)永遠(yuǎn)發(fā)生在主線程
- 僅持有單個(gè)且最新的數(shù)據(jù)
- 自動(dòng)取消訂閱
- 提供「可讀可寫」和「僅可讀」兩個(gè)版本收縮權(quán)限
- 配合 DataBinding 實(shí)現(xiàn)「雙向綁定」
觀察者的回調(diào)永遠(yuǎn)發(fā)生在主線程
因?yàn)長(zhǎng)iveData 是被用來更新 UI的,因此 Observer 接口的 onChanged() 方法必須在主線程回調(diào)。
public interface Observer<T> { void onChanged(T t); }
背后的道理也很簡(jiǎn)單,LiveData 的 setValue() 發(fā)生在主線程(非主線程調(diào)用會(huì)拋異常),而如果調(diào)用postValue()方法,則它的內(nèi)部會(huì)切換到主線程調(diào)用 setValue()。
protected void postValue(T value) { boolean postTask; synchronized (mDataLock) { postTask = mPendingData == NOT_SET; mPendingData = value; } if (!postTask) { return; } ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); }
可以看到,postValue()方法的內(nèi)部調(diào)用了postToMainThread()實(shí)現(xiàn)線程的切換,之后遍歷所有觀察者的 onChanged() 方法。
僅持有單個(gè)且最新數(shù)據(jù)
作為數(shù)據(jù)持有者,LiveData僅持有【單個(gè)且最新】的數(shù)據(jù)。單個(gè)且最新,意味著 LiveData 每次只能持有一個(gè)數(shù)據(jù),如果有新數(shù)據(jù)則會(huì)覆蓋上一個(gè)。并且,由于LiveData具備生命周期感知能力,所以觀察者只會(huì)在活躍狀態(tài)下(STARTED 到 RESUMED)才會(huì)接收到 LiveData 最新的數(shù)據(jù),在非活躍狀態(tài)下則不會(huì)收到。
自動(dòng)取消訂閱
可感知生命周期的重要優(yōu)勢(shì)就是可以自動(dòng)取消訂閱,這意味著開發(fā)者無需手動(dòng)編寫那些取消訂閱的模板代碼,降低了內(nèi)存泄漏的可能性。背后的實(shí)現(xiàn)邏輯是在生命周期處于 DESTROYED 時(shí),移除觀察者。
@Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); if (currentState == DESTROYED) { removeObserver(mObserver); return; } ... //省略其他代碼 }
提供「可讀可寫」和「僅可讀」兩種方式
LiveData 提供了setValue() 和 postValue()兩種方式來操作實(shí)體數(shù)據(jù),而為了細(xì)化權(quán)限,LiveData又提供了mutable(MutableLiveData) 和 immutable(LiveData) 兩個(gè)類,前者「可讀可寫」,后者則「僅可讀」。
配合 DataBinding 實(shí)現(xiàn)「雙向綁定」
LiveData 配合 DataBinding 可以實(shí)現(xiàn)更新數(shù)據(jù)自動(dòng)驅(qū)動(dòng)UI變化,如果使用「雙向綁定」還能實(shí)現(xiàn) UI 變化影響數(shù)據(jù)的變化功能。
2.2 LiveData的缺陷
正如前面說的,LiveData有自己的使用場(chǎng)景,只有滿足使用場(chǎng)景才會(huì)最大限度的發(fā)揮它的功能,而下面這些則是在設(shè)計(jì)時(shí)將自帶的一些缺陷:
- value 可以是 nullable 的
- 在 fragment 訂閱時(shí)需要傳入正確的 lifecycleOwner
- 當(dāng) LiveData 持有的數(shù)據(jù)是「事件」時(shí),可能會(huì)遇到「粘性事件」
- LiveData 是不防抖的
- LiveData 的 transformation 需要工作在主線程
value 可以是 nullable 的
由于LiveData的getValue() 是可空的,所以在使用時(shí)應(yīng)該注意判空,否則容易出現(xiàn)空指針的報(bào)錯(cuò)。
@Nullable public T getValue() { Object data = mData; if (data != NOT_SET) { return (T) data; } return null; }
傳入正確的 lifecycleOwner
Fragment 調(diào)用 LiveData的observe() 方法時(shí)傳入 this 和 viewLifecycleOwner 的含義是不一樣的。因?yàn)镕ragment與Fragment中的View的生命周期并不一致,有時(shí)候我們需要的讓observer感知Fragment中的View的生命周期而非Fragment。
粘性事件
粘性事件的定義是,發(fā)射的事件如果早于注冊(cè),那么注冊(cè)之后依然可以接收到的事件,這一現(xiàn)象稱為粘性事件。解決辦法是:將事件作為狀態(tài)的一部分,在事件被消費(fèi)后,不再通知觀察者。推薦兩種解決方式:
- KunMinX/UnPeek-LiveData
- 使用kotlin 擴(kuò)展函數(shù)和 typealias 封裝解決「粘性」事件的 LiveData
默認(rèn)不防抖
當(dāng)setValue()/postValue() 傳入相同的值且多次調(diào)用時(shí),觀察者的 onChanged() 也會(huì)被多次調(diào)用。不過,嚴(yán)格來講,這也不算一個(gè)問題,我們只需要在調(diào)用 setValue()/postValue() 前判斷一下 vlaue 與之前是否相同即可。
transformation 工作在主線程
有些時(shí)候,我們需要對(duì)從Repository 層得到的數(shù)據(jù)進(jìn)行處理。例如,從數(shù)據(jù)庫獲得 User列表,我們需要根據(jù) id 獲取某個(gè) User, 那么就需要用到MediatorLiveData 和 Transformatoins 來實(shí)現(xiàn)。
- Transformations.map
- Transformations.switchMap
并且,map 和 switchMap 內(nèi)部均是使用 MediatorLiveData的addSource() 方法實(shí)現(xiàn)的,而該方法會(huì)在主線程調(diào)用,使用不當(dāng)會(huì)有性能問題。
@MainThread public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) { Source<S> e = new Source<>(source, onChanged); Source<?> existing = mSources.putIfAbsent(source, e); if (existing != null && existing.mObserver != onChanged) { throw new IllegalArgumentException( "This source was already added with the different observer"); } if (existing != null) { return; } if (hasActiveObservers()) { e.plug(); } }
2.3 LiveData 小結(jié)
LiveData 是一種可觀察的數(shù)據(jù)存儲(chǔ)器類,與常規(guī)的可觀察類不同,LiveData 具有生命周期感知能力,它遵循其他應(yīng)用組件(如 Activity、Fragment 或 Service)的生命周期。這種感知能力可確保LiveData當(dāng)數(shù)據(jù)源發(fā)生變化的時(shí)候,通知它的觀察者更新UI界面。同時(shí)它只會(huì)通知處于Active狀態(tài)的觀察者更新界面,如果某個(gè)觀察者的狀態(tài)處于Paused或Destroyed時(shí)那么它將不會(huì)收到通知,所以不用擔(dān)心內(nèi)存泄漏問題。
同時(shí),LiveData 專注單一功能,因此它的一些方法使用上是有局限性的,并且需要配合 ViewModel 使用才能顯示其價(jià)值。
三、Flow
3.1 簡(jiǎn)介
Flow是Google官方提供的一套基于kotlin協(xié)程的響應(yīng)式編程模型,它與RxJava的使用類似,但相比之下Flow使用起來更簡(jiǎn)單,另外Flow作用在協(xié)程內(nèi),可以與協(xié)程的生命周期綁定,當(dāng)協(xié)程取消時(shí),F(xiàn)low也會(huì)被取消,避免了內(nèi)存泄漏風(fēng)險(xiǎn)。
協(xié)程是輕量級(jí)的線程,本質(zhì)上協(xié)程、線程都是服務(wù)于并發(fā)場(chǎng)景下,其中協(xié)程是協(xié)作式任務(wù),線程是搶占式任務(wù)。默認(rèn)協(xié)程用來處理實(shí)時(shí)性不高的數(shù)據(jù),請(qǐng)求到結(jié)果后整個(gè)協(xié)程就結(jié)束了。比如,有下面一個(gè)例子:
其中,紅框中需要展示的內(nèi)容實(shí)時(shí)性不高,而需要交互的,比如轉(zhuǎn)發(fā)和點(diǎn)贊屬于實(shí)時(shí)性很高的數(shù)據(jù)需要定時(shí)刷新。對(duì)于實(shí)時(shí)性不高的場(chǎng)景,直接使用 Kotlin 的協(xié)程處理即可,比如。
suspend fun loadData(): Data uiScope.launch { val data = loadData() updateUI(data) }
而對(duì)于實(shí)時(shí)性要求較高的場(chǎng)景,上面的方式就不起作用了,此時(shí)需要用到Kotlin提供的Flow數(shù)據(jù)流。
fun dataStream(): Flow<Data>uiScope.launch { dataStream().collect { data -> updateUI(data) } }
3.2 基本概念
Kotlin的數(shù)據(jù)流主要由三個(gè)成員組成,分別是生產(chǎn)者、消費(fèi)者和中介。 生產(chǎn)者:生成添加到數(shù)據(jù)流中的數(shù)據(jù),可以配合得協(xié)程使用,使用異步方式生成數(shù)據(jù)。 中介(可選):可以修改發(fā)送到數(shù)據(jù)流的值,或修正數(shù)據(jù)流本身。 消費(fèi)者:使用方則使用數(shù)據(jù)流中的值。
其中,中介可以對(duì)數(shù)據(jù)流中的數(shù)據(jù)進(jìn)行更改,甚至可以更改數(shù)據(jù)流本身,他們的架構(gòu)示意圖如下。
在Kotlin中,F(xiàn)low 是一種冷流,不過有一種特殊的Flow( StateFlow/SharedFlow) 是熱流。什么是冷流,他和熱流又有什么關(guān)系呢?
冷流:只有訂閱者訂閱時(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.3 StateFlow
前面說過,冷流和訂閱者只能是一對(duì)一的關(guān)系,當(dāng)我們要實(shí)現(xiàn)一個(gè)流多個(gè)訂閱者的場(chǎng)景時(shí),就需要使用熱流了。
StateFlow 是一個(gè)狀態(tài)容器式可觀察數(shù)據(jù)流,可以向其收集器發(fā)出當(dāng)前狀態(tài)更新和新狀態(tài)更新??梢酝ㄟ^其 value 屬性讀取當(dāng)前狀態(tài)值,如需更新狀態(tài)并將其發(fā)送到數(shù)據(jù)流,那么就需要使用MutableStateFlow。
基本使用
在Android 中,StateFlow 非常適合需要讓可變狀態(tài)保持可觀察的類。由于StateFlow并不是系統(tǒng)API,所以使用前需要添加依賴:
dependencies { ... //省略其他 implementation "androidx.activity:activity-ktx:1.3.1" implementation "androidx.fragment:fragment-ktx:1.4.1" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1' }
接著,我們需要?jiǎng)?chuàng)建一個(gè)ViewModel,比如:
class StateFlowViewModel: ViewModel() { val data = MutableStateFlow<Int>(0) fun add(v: View) { data.value++ } fun del(v: View) { data.value-- } }
可以看到,我們使用MutableStateFlow包裹需要操作的數(shù)據(jù),并添加了add()和del()兩個(gè)方法。然后,我們?cè)倬帉懸欢螠y(cè)試代碼實(shí)現(xiàn)數(shù)據(jù)的修改,并自動(dòng)刷新數(shù)據(jù)。
class StateFlowActivity : AppCompatActivity() { private val viewModel by viewModels<StateFlowViewModel>() private val mBinding : ActivityStateFlowBinding by lazy { ActivityStateFlowBinding.inflate(layoutInflater) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(mBinding.root) initFlow() } private fun initFlow() { mBinding.apply { btnAdd.setOnClickListener { viewModel.add(it) } btnDel.setOnClickListener { viewModel.del(it) } } } }
上面代碼中涉及到的布局代碼如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="stateFlowViewModel" type="com.xzh.demo.flow.StateFlowViewModel" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="200dp" android:layout_marginTop="30dp" android:text="@{String.valueOf(stateFlowViewModel.data)}" android:textSize="24sp" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/btn_add" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|start" android:layout_marginStart="10dp" android:layout_marginBottom="10dp" android:contentDescription="start" android:src="@android:drawable/ic_input_add" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/btn_del" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_marginEnd="10dp" android:layout_marginBottom="10dp" android:contentDescription="cancel" android:src="@android:drawable/ic_menu_close_clear_cancel" /> </FrameLayout> </layout>
上面代碼中,我們使用了DataBing寫法,因此不需要再手動(dòng)的綁定數(shù)據(jù)和刷新數(shù)據(jù)。
3.4 SharedFlow
SharedFlow基本概念
SharedFlow提供了SharedFlow 與 MutableSharedFlow兩個(gè)版本,平時(shí)使用較多的是MutableSharedFlow。它們的區(qū)別是,SharedFlow可以保留歷史數(shù)據(jù),MutableSharedFlow 沒有起始值,發(fā)送數(shù)據(jù)時(shí)需要調(diào)用 emit()/tryEmit() 方法。
首先,我們來看看SharedFlow的構(gòu)造函數(shù):
public fun <T> MutableSharedFlow( replay: Int = 0, extraBufferCapacity: Int = 0, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND ): MutableSharedFlow<T>
可以看到,MutableSharedFlow需要三個(gè)參數(shù):
- replay:表示當(dāng)新的訂閱者Collect時(shí),發(fā)送幾個(gè)已經(jīng)發(fā)送過的數(shù)據(jù)給它,默認(rèn)為0,即默認(rèn)新訂閱者不會(huì)獲取以前的數(shù)據(jù)
- extraBufferCapacity:表示減去replay,MutableSharedFlow還緩存多少數(shù)據(jù),默認(rèn)為0
- onBufferOverflow:表示緩存策略,即緩沖區(qū)滿了之后Flow如何處理,默認(rèn)為掛起。除此之外,還支持DROP_OLDEST 和DROP_LATEST 。
//ViewModel val sharedFlow=MutableSharedFlow<String>() viewModelScope.launch{ sharedFlow.emit("Hello") sharedFlow.emit("SharedFlow") } //Activity lifecycleScope.launch{ viewMode.sharedFlow.collect { print(it) } }
基本使用
SharedFlow并不是系統(tǒng)API,所以使用前需要添加依賴:
dependencies { ... //省略其他 implementation "androidx.activity:activity-ktx:1.3.1" implementation "androidx.fragment:fragment-ktx:1.4.1" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1' }
接下來,我們創(chuàng)建一個(gè)SharedFlow,由于需要一對(duì)多的進(jìn)行通知,所以我們MutableSharedFlow,然后重寫postEvent()方法,
代碼如下:
object LocalEventBus { private val events= MutableSharedFlow< Event>() suspend fun postEvent(event: Event){ events.emit(event) } } data class Event(val timestamp:Long)
接下來,我們?cè)賱?chuàng)建一個(gè)ViewModel,里面添加startRefresh()和cancelRefresh()兩個(gè)方法,
如下:
class SharedViewModel: ViewModel() { private lateinit var job: Job fun startRefresh(){ job=viewModelScope.launch (Dispatchers.IO){ while (true){ LocalEventBus.postEvent(Event(System.currentTimeMillis())) } } } fun cancelRefresh(){ job.cancel() } }
前面說過,一個(gè)典型的Flow是由三部分構(gòu)成的。所以,此處我們先新建一個(gè)用于數(shù)據(jù)消費(fèi)的Fragment
代碼如下:
class FlowFragment: Fragment() { private val mBinding : FragmentFlowBinding by lazy { FragmentFlowBinding.inflate(layoutInflater) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return mBinding.root } override fun onStart() { super.onStart() lifecycleScope.launchWhenCreated { LocalEventBus.events.collect { mBinding.tvShow.text=" ${it.timestamp}" } } } }
FlowFragment的主要作用就是接收LocalEventBus的數(shù)據(jù),并顯示到視圖上。接下來,我們還需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)的生產(chǎn)者,為了簡(jiǎn)單,我們只在生產(chǎn)者頁面中開啟協(xié)程,
代碼如下:
class FlowActivity : AppCompatActivity() { private val viewModel by viewModels<SharedViewModel>() private val mBinding : ActivityFlowBinding by lazy { ActivityFlowBinding.inflate(layoutInflater) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(mBinding.root) initFlow() } private fun initFlow() { mBinding.apply { btnStart.setOnClickListener { viewModel.startRefresh() } btnStop.setOnClickListener { viewModel.cancelRefresh() } } } }
其中,F(xiàn)lowActivity代碼中涉及的布局如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".fragment.SharedFragment"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <fragment android:name="com.xzh.demo.FlowFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|start" android:layout_marginStart="10dp" android:layout_marginBottom="10dp" android:src="@android:drawable/ic_input_add" android:contentDescription="start" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/btn_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_marginEnd="10dp" android:layout_marginBottom="10dp" android:src="@android:drawable/ic_menu_close_clear_cancel" android:contentDescription="cancel" /> </FrameLayout> </layout>
最后,當(dāng)我們運(yùn)行上面的代碼時(shí),就會(huì)在FlowFragment的頁面上顯示當(dāng)前的時(shí)間戳,并且頁面的數(shù)據(jù)會(huì)自動(dòng)進(jìn)行刷新。
3.5 冷流轉(zhuǎn)熱流
前文說過,Kotlin的Flow是一種冷流,而StateFlow/SharedFlow則屬于熱流。那么有人會(huì)問:怎么將冷流轉(zhuǎn)化為熱流呢?答案就是kotlin提供的shareIn()和stateIn()兩個(gè)方法。
首先,來看一下StateFlow的shareIn的定義:
public fun <T> Flow<T>.stateIn( scope: CoroutineScope, started: SharingStarted, initialValue: T ): StateFlow<T>
shareIn方法將流轉(zhuǎn)換為SharedFlow,需要三個(gè)參數(shù),我們重點(diǎn)看一下started參數(shù),表示流啟動(dòng)的條件,支持三種:
- SharingStarted.Eagerly:無論當(dāng)前有沒有訂閱者,流都會(huì)啟動(dòng),訂閱者只能接收到replay個(gè)緩沖區(qū)的值。
- SharingStarted.Lazily:當(dāng)有第一個(gè)訂閱者時(shí),流才會(huì)開始,后面的訂閱者只能接收到replay個(gè)緩沖區(qū)的值,當(dāng)沒有訂閱者時(shí)流還是活躍的。
- SharingStarted.WhileSubscribed:只有滿足特定的條件時(shí)才會(huì)啟動(dòng)。
接下來,我們?cè)诳匆幌耂haredFlow的shareIn的定義:
public fun <T> Flow<T>.shareIn( scope: CoroutineScope, started: SharingStarted, replay: Int = 0 ): SharedFlow<T>
此處,我們重點(diǎn)看下replay參數(shù),該參數(shù)表示轉(zhuǎn)換為SharedFlow之后,當(dāng)有新的訂閱者的時(shí)候發(fā)送緩存中值的個(gè)數(shù)。
3.6 StateFlow與SharedFlow對(duì)比
從前文的介紹可以知道,StateFlow與SharedFlow都是熱流,都是為了滿足流的多個(gè)訂閱者的使用場(chǎng)景的,一時(shí)間讓人有些傻傻分不清,那StateFlow與SharedFlow究竟有什么區(qū)別呢?總結(jié)起來,大概有以下幾點(diǎn):
- SharedFlow配置更為靈活,支持配置replay、緩沖區(qū)大小等,StateFlow是SharedFlow的特殊化版本,replay固定為1,緩沖區(qū)大小默認(rèn)為0。
- StateFlow與LiveData類似,支持通過myFlow.value獲取當(dāng)前狀態(tài),如果有這個(gè)需求,必須使用StateFlow。
- SharedFlow支持發(fā)出和收集重復(fù)值,而StateFlow當(dāng)value重復(fù)時(shí),不會(huì)回調(diào)collect給新的訂閱者,StateFlow只會(huì)重播當(dāng)前最新值,SharedFlow可配置重播元素個(gè)數(shù)(默認(rèn)為0,即不重播)。
從上面的描述可以看出,StateFlow為我們做了一些默認(rèn)的配置,而SharedFlow澤添加了一些默認(rèn)約束??偟膩碚f,SharedFlow相比StateFlow更靈活。
四、總結(jié)
目前,官方提供的可觀察的數(shù)據(jù)組件有LiveData、StateFlow和SharedFlow。LiveData是Android早期的數(shù)據(jù)流組件,具有生命周期感知能力,需要配合ViewModel才能實(shí)現(xiàn)它的價(jià)值。不過,LiveData也有很多使用場(chǎng)景缺陷,常見的有粘性事件、不支持防抖等。
于是,Kotlin在1.4.0版本,陸續(xù)推出了StateFlow與SharedFlow兩個(gè)組件,StateFlow與SharedFlow都是熱流,都是為了滿足流的多個(gè)訂閱者的使用場(chǎng)景,不過它們也有微妙的區(qū)別,具體參考前面內(nèi)容的說明。
到此這篇關(guān)于一文讀懂Android Kotlin的數(shù)據(jù)流的文章就介紹到這了,更多相關(guān)Android Kotlin內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android使用SurfaceView實(shí)現(xiàn)飄贊動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了Android如何使用SurfaceView實(shí)現(xiàn)飄贊動(dòng)畫,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Android?Flutter實(shí)現(xiàn)"斑馬紋"背景的示例代碼
本文將通過實(shí)現(xiàn)一個(gè)canvas繪制斑馬紋類。使用Stack布局,將斑馬紋放在下方作為背景板,需要展示的內(nèi)容在上方。從而實(shí)現(xiàn)?“斑馬紋”背景,感興趣的可以了解一下2022-06-06一文理解Android系統(tǒng)中強(qiáng)指針的實(shí)現(xiàn)
因?yàn)锳ndroid中很多地方代碼是用C++編寫,為了能夠保證C++中指針能夠被正確的釋放,于是Android引入了其實(shí)在C++中已經(jīng)有的智能指針技術(shù)2021-10-10Android 中build.prop 文件與 getprop 命令
這篇文章主要介紹了Android 中build.prop 文件與 getprop 命令的相關(guān)資料,需要的朋友可以參考下2017-06-06Android使用NestedScrollView?內(nèi)嵌RecycleView滑動(dòng)沖突問題解決
這篇文章主要介紹了Android使用NestedScrollView?內(nèi)嵌RecycleView滑動(dòng)沖突問題解決,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-06-06Android 倒計(jì)時(shí)控件 CountDownView的實(shí)例代碼詳解
這篇文章主要介紹了Android 倒計(jì)時(shí)控件 CountDownView的實(shí)例代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Android之Notification的多種用法實(shí)例
本篇文章主要介紹了Android之Notification的多種用法實(shí)例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12