前语
如今android开发基本上从之前的Java言语转而运用Kotlin言语,MMVM模式中用于保存UI状况的东西LiveData
也逐渐被Flow
代替。下面将逐渐介绍Kotlin的Flow相关知识,以及怎么与Coroutine
配合运用,写出漂亮的声明式,呼应式代码,当然最重要的是功用强壮,可读性强,易于保护!
Flow介绍
Flow 是 Kotlin 协程库中的一个概念和类,用于处理异步数据流。它供给了一种声明式的办法来处理连续的、异步的数据序列,而且与协程无缝集成。
以下是 Flow 的一些关键特性和优势
- 异步数据流:Flow 答应以异步的办法处理连续的数据流。它能够处理很多的数据或长时间运转的操作,而无需阻塞主线程。
- 声明式编程:Flow 供给了一种声明式的编程模型,经过操作符(operators)链式调用来处理数据流。这使得代码更简练、易读和易于保护。
- 可组合性:Flow 的操作符能够组合在一同,构建杂乱的数据转换和处理逻辑。您能够运用
map
、filter
、flatMap
、zip
等操作符来转换、过滤、合并和组合数据流。 - 挂起函数:Flow 的操作能够在挂起函数中履行,使其适用于与协程一同运用。这样能够方便地进行异步操作和并发编程,避免了回调阴间和杂乱的线程办理。
- 撤销支撑:Flow 具有与协程相同的撤销支撑。能够运用
cancel
、collect
中的cancellable
参数或withTimeout
等函数来撤销数据流的搜集和处理。
在介绍flow详细用法之前,先说明下flow的一些概念:
Flow组成
- Producers(生产者):数据流的产生
emit
- Customers(顾客):数据流的搜集
collect
- Operators(中心操作符):数据流的二次加工
flow的冷流&暖流
在 Kotlin 的协程中,”冷流”(Cold Flow)和”暖流”(Hot Flow)是用来描述 Flow 和 SharedFlow 两种不同的数据流的特性,还有一种特别的暖流,StateFlow,它承继自SharedFlow
public interface StateFlow<out T> : SharedFlow<T> {
/**
* The current value of this state flow.
*/
public val value: T
}
cold flow & hot flow差异
-
冷流(Cold Flow):
- 冷流是指每次订阅都会从头开端并独立运转的数据流。
- 当每个订阅者开端搜集数据时,冷流会从头开端发射数据,每个订阅者都会独立地接收到完整的数据流。
- 例如,经过调用 Flow 的
collect
或collectLatest
函数,能够订阅冷流并搜集数据。
-
暖流(Hot Flow):
- 暖流是指现已开端发射数据并在订阅之前运转的数据流。
- 暖流在启动时就开端发射数据,不管是否有订阅者。
- 假如订阅者在流现已开端发射数据后加入,它们可能会错过一些数据。
- 例如,经过调用 SharedFlow 的
asSharedFlow
函数,能够创立暖流,并能够经过collect
函数订阅。
Flow运用
class SecondFragment : Fragment() {
//……省掉无关代码
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonFlow.setOnClickListener {
lifecycleScope.launch {
val value = createFlow().first()
Log.d("flow", "flow.first() = $value")
val acc = createFlow().fold(0) { acc, item ->
acc + item
}
Log.d("flow", "flow.fold() = $acc")
try {
val value = createFlow().single()
Log.d("flow", "flow.single() = $value")
} catch (e: Exception) {
Log.d("flow", e.toString())
}
}
}
binding.collectLastBtn.setOnClickListener {
lifecycleScope.launch {
createFlow().collectLatest { value ->
println("Collecting $value")
delay(1000) // Emulate work
println("$value collected")
}
}
}
}
private fun createFlow(): Flow<Int> {
return flow {
emit(100)
delay(500)
emit(200)
emit(300)
}
}
}
flow创立
创立一个普通flow很简略,直接如上所述办法createFlow()
,直接调用flow{}
,代码块中运用emit(value)
发射数据;别的还有一些其他办法创立flow,例如T.asFlow()
和flowOf(value: T)
等办法,本质都是调用了flow{}
,详细运用细节看后续Demo;
public fun <T> Iterable<T>.asFlow(): Flow<T> = flow {
forEach { value ->
emit(value)
}
}
flow的常用操作符
first
顾名思义获取到flow数据流中的第一个元素,与之对应的是last()
fold
这个办法源码如下:需求一个参数初始值,用于后续(acc: R, value: T) -> R
函数的入参acc
,经过collect
得到flow发射的每一个值,调用operation
,返回终究得到的计算成果;
public suspend inline fun <T, R> Flow<T>.fold(
initial: R,
crossinline operation: suspend (acc: R, value: T) -> R
): R {
var accumulator = initial
collect { value ->
accumulator = operation(accumulator, value)
}
return accumulator
}
例如:得到的计算成果就是100+200+300 = 600,终究打印flow.fold() = 600
val acc = createFlow().fold(0) { acc, item ->
acc + item
}
Log.d("flow", "flow.fold() = $acc")
single
上述例子中有这样一段code:
try {
val value = createFlow().single()
Log.d("flow", "flow.single() = $value")
} catch (e: Exception) {
Log.d("flow", e.toString())
}
这儿的single()
操作符效果如下:
-
获取单个元素:
single()
操作符用于获取 Flow 中的单个元素。假如 Flow 中只包括一个元素,它将返回该元素;假如 Flow 中包括多个元素或没有元素,它将抛出IllegalArgumentException
异常。 -
用于保证 Flow 只包括一个元素:
single()
能够用作 Flow 的查看机制,保证 Flow 中只包括一个元素。假如 Flow 中的元素数量不符合预期,single()
将抛出异常,供给了一种简略的验证和安全性查看。 -
简化处理单个元素的状况:当你只关心 Flow 中的单个元素,并期望在处理该元素时终止流的搜集时,能够运用
single()
。它能够简化对单个元素的处理逻辑。
distinctUntilChanged
数据去重
createFlow().distinctUntilChanged().collectLatest {
println("emit value = $it")
}
StateFlow
创立
stateFlow初始化的时分必须要有一个初始值
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
用法也很简略,几乎和LiveData相同,都有一个value
属性,赋值都是给value
赋值
private val _stateFlow = MutableStateFlow("Hello world")
val stateFlow: StateFlow<String> = _stateFlow.asStateFlow()
fun triggerStateFlow() {
_stateFlow.value = "StateFlow"
}
运用
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.stateFlow.collectLatest {
binding.stateText.text = it
Snackbar.make(
binding.root,
it,
Snackbar.LENGTH_LONG
).show()
}
}
}
}
每次给stateflow.value
赋值,都会触发collect
办法,相似livedata.observe(this)
,
只不过collect是协程挂起函数,需求在Coroutine.Scope
中履行代码块。
怎么与LifecycleScope协作
这儿Lifecycle.repeatOnLifecycle
用法如上图所示,趁便说下该repeatOnLifecycle
是根据lifecycte-runtime-ktx:2.4.0
版别才有的新接口,假如你的没有找到该api,请查看你的库版别。
要解说这儿为什么要这么运用需求了解activity/fragment
生命周期
当咱们直接运用:
lifecycleScope.launch {
viewModel.triggerFlow().collectLatest {
binding.flowText.text = it
}
}
这种办法是不安全的,当app进入后台的时分,生命周期函数是走到onStop
,但是此刻flow所在的协程还是处在活泼状况,能够正常搜集数据,这就造成了数据的糟蹋,乃至产生内存走漏现象如下图所示;
当咱们运用repeatOnLifecycle(Lifecycle.State.STARTED)
的时分,看下图:
当app进入后台的时分咱们的协程挂起函数会处于挂起状况,此刻会停止搜集flow;从头进入前台后,挂起函数会从头运转;
数据防抖动
StateFlow和LiveData一个重要的差异在于,LiveData在重复设置value
为相同值的状况下,会重复触发observe
回调, 它是不防抖的;
StateFlow 防抖,它天生有去重的功用!效果相似Flow.distinctUntilChanged()
这是由于它的源码中有这段逻辑:
private fun updateState(expectedState: Any?, newState: Any): Boolean {
var curSequence = 0
var curSlots: Array<StateFlowSlot?>? = this.slots // benign race, we will not use it
synchronized(this) {
val oldState = _state.value
if (expectedState != null && oldState != expectedState) return false // CAS support
if (oldState == newState) return true
stateflow
调用distinctUntilChanged
会报错如下
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Applying 'distinctUntilChanged' to StateFlow has no effect. See the StateFlow documentation on Operator Fusion.",
replaceWith = ReplaceWith("this")
)
public fun <T> StateFlow<T>.distinctUntilChanged(): Flow<T> = noImpl()
粘性数据(数据倒灌)
当屏幕翻转或跳转返回,或者弹Dialog的时分,stateFlow会产生数据倒灌,stateflow的value会从头发送给顾客,触发collect代码块; 这与LiveData是共同的,后边ShareFlow会讲到怎么避免这种状况!
SharedFlow
创立
SharedFlow和StateFlow相同,SharedFlow
也有两个版别:SharedFlow
与MutableSharedFlow
。
private val _sharedFlow = MutableSharedFlow<String>()
val sharedFlow = _sharedFlow.asSharedFlow()
fun triggerSharedFlow() {
viewModelScope.launch {
_sharedFlow.emit("SharedFlow")
}
}
初始化办法:
public fun <T> MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
)
它和StateFlow差异在于
- 没有初始值;
- SharedFlow能够保存历史数据,stateFlow只会保存最新的值;
- SharedFlow发射数据用
emit
,没有setValue
办法;
stateFlow承继自SharedFlow
StateFlow是SharedFlow的一种特殊用途、高功用且高效的完成,用于狭隘但广泛运用的同享状况的状况。有关适用于一切同享流的基本规则、约束和运算符,请参阅SharedFlow文档。
StateFlow一直有一个初始值,向新订阅者重播一个最新值,不再缓冲任何其他值,但保存最后发出的值,而且不支撑ResetReplayCache。当运用以下参数创立StateFlow并对其运用distinctUntilChanged
运算符时,StateFlow的行为与同享流相同:
// MutableStateFlow(initialValue) is a shared flow with the following parameters:
val shared = MutableSharedFlow(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
shared.tryEmit(initialValue) // emit the initial value
val state = shared.distinctUntilChanged() // get StateFlow-like behavior
当您需求对StateFlow的行为进行调整(例如额定缓冲、重播更多值或省掉初始值)时,请运用SharedFlow 。
运用
运用和StateFlow 相似
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.sharedFlow.collectLatest {
binding.shareText.text = it
}
}
}
}
默许状况下,replay = 0,当有新的订阅者的时分,SharedFlow不会向它发送数据。这儿有点像通知的感觉。
正由于默许状况下 replay = 0,SharedFlow不会有数据倒灌的状况产生。 详细查看Demo演示。
总结
Flow 供给了一种简练、强壮且可组合的办法来处理异步数据流。它能够与 Kotlin 协程一同运用,为异步编程供给了更优雅的解决方案,并供给了更好的可读性和保护性。Flow 的规划使得处理数据流变得愈加直观和简略,一起具有高效和可扩展的特性。
Flow、StateFlow和SharedFlow是Kotlin协程库中用于处理异步数据流的不同类型。它们适用于不同的运用场景:
-
Flow:
- Flow适用于一次性的、连续的异步数据流。
- 运用Flow能够处理潜在的无限数据流,并在每次订阅时从头开端。
- Flow是冷流,每个订阅者都会独立地接收到完整的数据流。
- 合适处理单个值、调集、网络请求、数据库查询等异步操作的成果。
- 操作符链式调用的声明式编程风格使代码易于理解和组合。
-
StateFlow:
- StateFlow适用于具有状况的异步数据流。
- 它是
SharedFlow
的一个特化版别,用于表明具有可变状况的数据流。 - StateFlow保护当时的状况值,并将状况变化经过Flow的办法进行广播。
- 合适在UI层面中运用,能够完成简略的状况办理,例如表明UI组件的可见性、文本内容等。
-
SharedFlow:
- SharedFlow适用于多个订阅者同享的异步数据流。
- 它是一种暖流,即在开端发射数据后,不管是否有订阅者,都会继续发射数据。
- SharedFlow答应多个订阅者一起收到相同的数据流,而不是每个订阅者都从头开端数据流。
- 合适完成事件总线、实时更新、广播消息等场景,能够让多个订阅者调查和呼应相同的数据。
-
StateFlow
在遇到数据倒灌的状况下,数据倒灌不是问题,在某些场景下咱们不需求数据倒灌,能够采用SharedFlow
代替;
根据您的运用需求,能够挑选合适的数据流类型。假如只需求一次性的连续数据流,能够运用Flow。假如需求具有可变状况的数据流,能够运用StateFlow。假如需求多个订阅者同享相同的数据流,能够运用SharedFlow。
注意,Flow、StateFlow和SharedFlow都需求在协程效果域内进行搜集和处理,以保证正确的协程上下文和撤销支撑。
参阅
官方文档StateFlow&SharedFlow