一、为什么要使用 Flow?
LiveData 是 androidx 包下的组件,是 Android 生态中一个的简单的生命周期感知型容器。简单即是它的优势,也是它的局限,当然这些局限性不应该算 LiveData 的缺点,因为 LiveData 的设计初衷就是一个简单的数据容器。对于简单的数据流场景,使用 LiveData 完全没有问题。
LiveData 有以下几个局限性:
-
LiveData 只能在主线程更新数据: 只能在主线程 setValue,即使 postValue 内部也是切换到主线程执行;
-
LiveData 数据重放问题: 注册新的订阅者,会重新收到 LiveData 存储的数据,这在有些情况下不符合预期(可以使用自定义的 LiveData 子类
SingleLiveData解决); -
LiveData 不防抖: 重复 setValue 相同的值,订阅者会收到多次
onChanged()回调(可以使用distinctUntilChanged()解决); -
LiveData 不支持背压: 在数据生产速度 > 数据消费速度时,LiveData 无法正常处理。比如在子线程大量
postValue数据但主线程消费跟不上时,中间就会有一部分数据被忽略。
因此推出Flow,Flow的特点如下:
- Flow 支持协程: Flow 基于协程基础能力,能够以结构化并发的方式生产和消费数据,能够实现线程切换(依靠协程的 Dispatcher);
- Flow 支持数据重放配置: Flow 的子类 SharedFlow 支持配置重放 replay,能够自定义对新订阅者重放数据的配置;
- Flow 防抖: Flow 的子类 StateFlow 支持数据防抖,意味着仅在更新值并且发生变化才会回调,如果更新值没有变化不会回调 collect,其实就是在发射数据时加了一层拦截。
- Flow 支持背压: Flow 的子类 SharedFlow 支持配置缓存容量,可以应对数据生产速度 > 数据消费速度的情况;
- Flow 不是生命周期感知型组件: Flow 不是 Android 生态下的产物,自然 Flow 是不会关心组件生命周期。那么我们如何确保订阅者在监听 Flow 数据流时,不会在错误的状态更新 View 呢?Google 推荐的做法是使用
Lifecycle#repeatOnLifecycle。
二、冷数据流 Flow
冷流有以下几个特点:
- 冷流是不共享的,也没有缓存机制;
- 冷流只有在订阅者 collect 数据时,才按需执行发射数据流的代码,并且每次 collect 都会创建一个全新的数据流;
- 冷流和订阅者是一对一的关系,多个订阅者间的数据流是相互独立的,一旦订阅者停止监听或者生产代码结束,数据流就自动关闭。
下面看个例子:
fun testFlow(){
val scope = CoroutineScope(Dispatchers.IO+ Job())
scope.launch {
println("scope launch")
val flow = flow<Int> {
println("start emit")
emit(100)
println("end emit")
}
delay(2000)
println("start collect")
flow.collect{
println("collect result : $it")
}
println("start collect second")
flow.collect{
println("collect result second : $it")
}
}
}
输出:
scope launch
start collect
start emit
collect result : 100
end emit
start collect second
start emit
collect result second : 100
end emit
三、热数据流 SharedFlow
SharedFlow 和 StateFlow 都属于热流,它们都有一个可变的版本 MutableSharedFlow 和 MutableStateFlow,这与 LiveData 和 MutableLiveData 类似,对外暴露接口时,应该使用不可变的版本。
热流相较于冷流,热流无论是否有订阅者(collect),都可以生产数据并且缓存。
3.1、MutableSharedFlow
看下 MutableSharedFlow 源码:
public fun <T> MutableSharedFlow(
// 重放数据个数
replay: Int = 0,
// 额外缓存容量
extraBufferCapacity: Int = 0,
// 缓存溢出策略
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
val bufferCapacity0 = replay + extraBufferCapacity
val bufferCapacity = if (bufferCapacity0 < 0) Int.MAX_VALUE else bufferCapacity0 // coerce to MAX_VALUE on overflow
return SharedFlowImpl(replay, bufferCapacity, onBufferOverflow)
}
public enum class BufferOverflow {
// 挂起
SUSPEND,
// 丢弃最早的一个
DROP_OLDEST,
// 丢弃最近的一个
DROP_LATEST
}
MutableSharedFlow 构造函数允许我们配置三个参数:
| 参数 | 描述 |
|---|---|
| reply | 重放数据个数,当新订阅者时注册时会重放缓存的 replay 个数据 |
| extraBufferCapacity | 额外缓存容量,在 replay 之外的额外容量,SharedFlow 的缓存容量 capacity = replay + extraBufferCapacity |
| onBufferOverflow | 缓存溢出策略,即缓存容量 capacity 满时的处理策略(SUSPEND、DROP_OLDEST、DROP_LAST) |
SharedFlow 默认容量 capacity 为 0,重放 replay 为 0,缓存溢出策略是 SUSPEND,发射数据时已注册的订阅者会收到数据,但数据会立刻丢弃,而新的订阅者不会收到历史发射过的数据。
3.2、MutableSharedFlow 转为 SharedFlow
public fun <T> MutableSharedFlow<T>.asSharedFlow(): SharedFlow<T> =
ReadonlySharedFlow(this, null)
3.3、Flow 转为 SharedFlow
前面提到过,冷流是不共享的,也没有缓存机制。使用 Flow.shareIn 可以把冷流转换为热流,一来可以将数据共享给多个订阅者,二来可以增加缓冲机制。
public fun <T> Flow<T>.shareIn(
// 协程作用域范围
scope: CoroutineScope,
// 启动策略
started: SharingStarted,
// 控制数据重放的个数
replay: Int = 0
): SharedFlow<T> {
val config = configureSharing(replay)
val shared = MutableSharedFlow<T>(
replay = replay,
extraBufferCapacity = config.extraBufferCapacity,
onBufferOverflow = config.onBufferOverflow
)
@Suppress("UNCHECKED_CAST")
scope.launchSharing(config.context, config.upstream, shared, started, NO_VALUE as T)
return shared.asSharedFlow()
}
public companion object {
// 热启动式:立即开始,并在 scope 指定的作用域结束时终止
public val Eagerly: SharingStarted = StartedEagerly()
// 懒启动式:在注册首个订阅者时开始,并在 scope 指定的作用域结束时终止
public val Lazily: SharingStarted = StartedLazily()
public fun WhileSubscribed(
stopTimeoutMillis: Long = 0,
replayExpirationMillis: Long = Long.MAX_VALUE
): SharingStarted =
StartedWhileSubscribed(stopTimeoutMillis, replayExpirationMillis)
}
sharedIn 的参数 scope 和 replay 不需要过多解释,主要介绍下 started: SharingStarted 启动策略,分为三种:
| 参数 | 描述 |
|---|---|
| Eagerly(热启动式) | 立即启动数据流,并保持数据流(直到 scope 指定的作用域结束) |
| Lazily(懒启动式) | 在首个订阅者注册时启动,并保持数据流(直到 scope 指定的作用域结束) |
| WhileSubscribed() | 在首个订阅者注册时启动,并保持数据流直到在最后一个订阅者注销时结束(或直到 scope 指定的作用域结束) |
通过 WhildSubscribed() 策略能够在没有订阅者的时候及时停止数据流,避免引起不必要的资源浪费,例如一直从数据库、传感器中读取数据。
whileSubscribed() 还提供了两个配置参数:
- stopTimeoutMillis 超时时间(毫秒): 最后一个订阅者注销订阅后,保留数据流的超时时间,默认值 0 表示立刻停止。这个参数能够帮助防抖,避免订阅者临时短时间注销就马上关闭数据流。例如希望等待 5 秒后没有订阅者则停止数据流,可以使用 whileSubscribed(5000)。
- replayExpirationMillis 重放过期时间(毫秒): 停止数据流后,保留重放数据的超时时间,默认值 Long.MAX_VALUE 表示永久保存(replayExpirationMillis 发生在停止数据流后,说明 replayExpirationMillis 时间是在 stopTimeoutMillis 之后发生的)。例如希望希望等待 5 秒后停止数据流,再等待 5 秒后的数据视为无用的陈旧数据,可以使用 whileSubscribed(5000, 5000)。
四、热数据流 StateFlow
StateFlow 是 SharedFlow 的子接口,可以理解为一个特殊的 SharedFlow。
4.1、MutableStateFlow
看下 MutableStateFlow 源码:
public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
// 当前值
public override var value: T
// 比较并设置(通过 equals 对比,如果值发生真实变化返回 true)
public fun compareAndSet(expect: T, update: T): Boolean
}
MutableStateFlow 的构造函数就简单多了,有且仅有一个必选的参数,代表初始值:
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
StateFlow 是 SharedFlow 的一种特殊配置,MutableStateFlow(initialValue) 这样一行代码本质上和下面使用 SharedFlow 的方式是完全相同的:
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 初始化时必须传入初始值;
-
容量为 1: StateFlow 只会保存一个值;
-
重放为 1: StateFlow 会向新订阅者重放最新的值;
-
不支持 resetReplayCache() 重置重放缓存: StateFlow 的 resetReplayCache() 方法抛出
UnsupportedOperationException; -
缓存溢出策略为 DROP_OLDEST: 意味着每次发射的新数据会覆盖旧数据。
-
数据防抖: 意味着仅在更新值并且发生变化才会回调,如果更新值没有变化不会回调 collect,其实就是在发射数据时加了一层拦截
compareAndSet。
4.2、MutableStateFlow 转为 StateFlow
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> =
ReadonlyStateFlow(this, null)
4.3、Flow 转为 StateFlow
跟 SharedFlow 一样,普通 Flow 也可以转换为 StateFlow。
public fun <T> Flow<T>.stateIn(
// 共享开始时所在的协程作用域范围
scope: CoroutineScope,
// 共享开始策略
started: SharingStarted,
// 初始值
initialValue: T
): StateFlow<T> {
val config = configureSharing(1)
val state = MutableStateFlow(initialValue)
scope.launchSharing(config.context, config.upstream, state, started, initialValue)
return state.asStateFlow()
}
五、Flow 生命周期安全问题
前面也提到了,Flow 不具备 LiveData 的生命周期感知能力,所以订阅者在监听 Flow 数据流时,会存在生命周期安全的问题。Google 推荐的做法是使用 Lifecycle#repeatOnLifecycle。
| 方法 | 描述 |
|---|---|
| Activity.lifecycleScope.launch | 立即启动协程,并在 Activity 销毁时取消协程 |
| Fragment.lifecycleScope.launch | 立即启动协程,并在 Fragment 销毁时取消协程 |
| Fragment.viewLifecycleOwner.lifecycleScope.launch | 立即启动协程,并在 Fragment 中视图销毁时取消协程 |
| LifecycleContinueScope.launchWhenX | 在生命周期到达指定状态时立即启动协程执行代码块,在生命周期低于该状态时挂起(而不是取消)协程,在生命周期重新高于指定状态时,自动恢复该协程 |
| Lifecycle.repeatOnLifecycle | 在生命周期到达指定状态时立即启动协程执行代码块,在生命周期低于该状态时取消协程,在生命周期重新高于指定状态时,自动启动该协程。 |
| Flow.flowWithLifecycle | 内部基于 Lifecycle.repeatOnLifecycle |
可以看到:
- 1 - 3:这些协程 API 只有在最后组件 / 视图销毁时才会取消协程,当视图进入后台时协程并不会被取消,Flow 会持续生产数据,并且会触发更新视图;
- 4:这些协程 API 在视图离开某个状态时会挂起协程,能够避免更新视图。但是 Flow 会持续生产数据,也会产生一些不必要的操作和资源消耗(CPU 和内存);
- 5 - 6:很好的避免了一些不必要的操作和资源消耗(CPU 和内存)。
repeatOnLifecycle 常见用法:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED){
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
}
}
回过头来看,repeatOnLifecycle 是怎么实现生命周期感知的呢?其实很简单,是通过 Lifecycle#addObserver 来监听生命周期变化。
suspendCancellableCoroutine<Unit> { cont ->
// Lifecycle observers that executes `block` when the lifecycle reaches certain state, and
// cancels when it falls below that state.
val startWorkEvent = Lifecycle.Event.upTo(state)
val cancelWorkEvent = Lifecycle.Event.downFrom(state)
val mutex = Mutex()
observer = LifecycleEventObserver { _, event ->
if (event == startWorkEvent) {
// Launch the repeating work preserving the calling context
launchedJob = this@coroutineScope.launch {
// Mutex makes invocations run serially,
// coroutineScope ensures all child coroutines finish
mutex.withLock {
coroutineScope {
block()
}
}
}
return@LifecycleEventObserver
}
if (event == cancelWorkEvent) {
launchedJob?.cancel()
launchedJob = null
}
if (event == Lifecycle.Event.ON_DESTROY) {
cont.resume(Unit)
}
}
this@repeatOnLifecycle.addObserver(observer as LifecycleEventObserver)
}