LiveData、Flow

1,341 阅读7分钟

LiveData

LiveData是一个简单的生命周期感知组件,适合简单的数据流场景;但也有几个问题

  1. liveData更新不能放在子线程
  2. LiveData数据重放问题(SingleLiveData)
  3. 不防抖:重复 setValue 相同的值,订阅者会收到多次 onChanged() 回调(distinctUntilChanged())
  4. LiveData 不支持背压: 在数据生产速度 > 数据消费速度时,LiveData 无法正常处理。比如在子线程大量 postValue 数据但主线程消费跟不上时,中间就会有一部分数据被忽略。

Flow

  1. 支持协程,实现线程切换(Dispatcher)
  2. Flow支持背压:Flow的子类SharedFlow支持背压
  3. Flow支持数据重放配置
  4. 但Flow不是Android生命周期感知组件

冷热数据流

Flow包含三个实体,数据生产方-中介者(可选)-数据消费者,根据生产方产生数据的时机,kotlin 数据流分为冷流和热流

冷流

冷流是不共享的,也没有缓存机制,冷流只有在订阅者collect时才会执行发射数据的代码;冷流和订阅者时一对一的关系

热流

sharedFlow/stateFlow:热流是共享的,有缓存机制.无论是否有订阅者collect,都可以产生数据并缓存起来.热流和订阅者是一对多的关系,多个订阅者可以共享一个数据流,

冷流分析

冷流是不共享的,也没有缓存机制,数据源只有在消费者开始监听时才生产数据(collect()),并且每次开始监听后都会创建一个全新的数据流,一旦消费者停止监听或者生产者代码执行结束,Flow就会自动关闭.

  1. 使用 collect 方法的返回值取消流的收集

可以通过 Job 取消协程。如下所示:

kotlin
复制代码
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
​
fun main() = runBlocking {
    val job = launch {
        coldFlow.collect { data ->
            // 处理数据
            println(data)
        }
    }
    
    delay(5000)  // 例如,等待5秒后停止收集
    job.cancel() // 取消收集
}

2 .使用 takeWhiletake 操作符

可以在流的收集过程中通过条件来限制收集的时间:

kotlin
复制代码
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
​
fun main() = runBlocking {
    val coldFlow: Flow<Int> = flow {
        var result = 0
        while(true) {
            // 执行计算
            emit(result)
            result++
            delay(100)
        }
    }
​
    coldFlow.takeWhile { it < 50 }.collect { data ->
        // 处理数据
        println(data)
    }
}

3 .使用 withTimeoutOrNull 限制收集时间

可以使用 withTimeoutOrNull 来设置超时限制:

kotlin
复制代码
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
​
fun main() = runBlocking {
    val coldFlow: Flow<Int> = flow {
        var result = 0
        while(true) {
            // 执行计算
            emit(result)
            result++
            delay(100)
        }
    }
​
    withTimeoutOrNull(5000) { // 例如,限制收集时间为5秒
        coldFlow.collect { data ->
            // 处理数据
            println(data)
        }
    }
    println("收集已停止")
}

当每次消费者调用collect方法时,collector代码块就会重新执行一次,也就是重新执行一次数据产生的代码:

public abstract class AbstractFlow<T> : Flow<T>, CancellableFlow<T> {
​
    public final override suspend fun collect(collector: FlowCollector<T>) {
        val safeCollector = SafeCollector(collector, coroutineContext)
        try {
            collectSafely(safeCollector)
        } finally {
            safeCollector.releaseIntercepted()
        }
    }
​
    /**
     * Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
     *
     * A valid implementation of this method has the following constraints:
     * 1) It should not change the coroutine context (e.g. with `withContext(Dispatchers.IO)`) when emitting values.
     *    The emission should happen in the context of the [collect] call.
     *    Please refer to the top-level [Flow] documentation for more details.
     * 2) It should serialize calls to [emit][FlowCollector.emit] as [FlowCollector] implementations are not
     *    thread-safe by default.
     *    To automatically serialize emissions [channelFlow] builder can be used instead of [flow]
     *
     * @throws IllegalStateException if any of the invariants are violated.
     */
    public abstract suspend fun collectSafely(collector: FlowCollector<T>)
}
​

这里用到了kotlin的很多特性,这里可以顺带提一下:

  1. 首先我们来看FlowCollector的定义:

    public fun interface FlowCollector<in T> {
    ​
        /**
         * Collects the value emitted by the upstream.
         * This method is not thread-safe and should not be invoked concurrently.
         */
        public suspend fun emit(value: T)
    }
    

    这里定义了一个FlowCollector接口,并且使用了in T的类型参数标记,它一般用于泛型类或者泛型接口,来表示泛型参数的使用方式,in T 表示对T类型参数的逆变

    逆变(Contravariant)

    逆变 意味着泛型类型参数可以用其超类型来替代。使用 in 关键字标记泛型类型参数表示这个类型只能被消费(作为输入参数),而不能被生产(作为返回类型)。

    kotlin
    复制代码
    public fun interface FlowCollector<in T> {
        public suspend fun emit(value: T)
    }
    

    FlowCollector<in T> 中,T 只能用于输入位置(例如 emit 方法的参数),不能用于输出位置。这意味着你可以使用 FlowCollector 来处理 T 类型的任何子类型的数据。例如,如果你有一个 FlowCollector<Number>,你可以传递一个 FlowCollector<Int> 给它,因为 IntNumber 的子类型.

    逆变类型参数在一些场景中是非常有用的,特别是在设计只能消费类型参数的接口或类时。对于 FlowCollector<T> 来说,它主要是用来消费流中的值,即 emit(value: T) 方法。因此,将 T 标记为 in 确保了类型的安全和灵活性。

    协变(Covariant) :使用 out 关键字,表示泛型类型参数只能作为输出使用。

  2. 对于AbstractFlow的实现类SafeFlow

    // Named anonymous object
    private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() {
        override suspend fun collectSafely(collector: FlowCollector<T>) {
            collector.block()
        }
    }
    

    private val block: suspend FlowCollector.() -> Unit 表示:

    1. suspend表示这是一个挂起函数,可以在协程里调用;
    2. FlowCollector.() 表示这是一个带有接收者的函数类型,它表示这个函数类型是在FlowCollector的上下文执行,也就是说block的接收者是FlowCollect,在block中可以调用FlowCollect中的方法(比如emit方法)
    3. Unit表示这个函数不返回任何值

热流分析

SharedFLow和StateFlow都属于热流,无论是否有订阅者都会产生数据并缓存

SharedFlow

public interface SharedFlow<out T> : Flow<T> {
    /**
     * 重放缓存的快照
     */
    public val replayCache: List<T>
​
    /**
     * Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
     * To emit values from a shared flow into a specific collector, either `collector.emitAll(flow)` or `collect { ... }`
     * SAM-conversion can be used.
     *
     * **A shared flow never completes**. A call to [Flow.collect] or any other terminal operator
     * on a shared flow never completes normally.
     *
     * @see [Flow.collect] for implementation and inheritance details.
     */
    override suspend fun collect(collector: FlowCollector<T>): Nothing
}

这里有一点需要注意一下,Flow中collect的方法:

public interface Flow<out T> {
​
    /**
     * Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
     *
     * This method can be used along with SAM-conversion of [FlowCollector]:
     * ```
     * myFlow.collect { value -> println("Collected $value") }
     * ```
     *
     * ### Method inheritance
     *
     * To ensure the context preservation property, it is not recommended implementing this method directly.
     * Instead, [AbstractFlow] can be used as the base type to properly ensure flow's properties.
     *
     * All default flow implementations ensure context preservation and exception transparency properties on a best-effort basis
     * and throw [IllegalStateException] if a violation was detected.
     */
    public suspend fun collect(collector: FlowCollector<T>)
}

collect方法的返回值是Unit,表示返回值为空(类似于Java中的void),如果函数不返回任何有意义的值,那就返回Unit;

而到了SharedFlow中,返回值Nothing;Nothing是kotlin中的所有类的子类,表示不会返回任何值的表达式的类型。通常用于表示从不正常返回的函数的返回类型,或者在表达式导致程序终止的情况下的返回类型。

MutableSharedFlow

public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
     //只有在缓冲区满时溢出策略为suspend时才会挂起
     //如果没有订阅者,最近发送的数据都会存储在重放缓存中,
    override suspend fun emit(value: T)
​
     //返回值为true则说明发射成功,false
    public fun tryEmit(value: T): Boolean//活跃订阅者数量
    public val subscriptionCount: StateFlow<Int>
  
    //重置重放缓存,新订阅者只会收到注册后新发射的数据
    @ExperimentalCoroutinesApi
    public fun resetReplayCache()
}

构造一个SharedFlow

public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
    require(replay >= 0) { "replay cannot be negative, but was $replay" }
    require(extraBufferCapacity >= 0) { "extraBufferCapacity cannot be negative, but was $extraBufferCapacity" }
    require(replay > 0 || extraBufferCapacity > 0 || onBufferOverflow == BufferOverflow.SUSPEND) {
        "replay or extraBufferCapacity must be positive with non-default onBufferOverflow strategy $onBufferOverflow"
    }
    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对象,首先需要注意的是一般来说kotlin的方法名会采用驼峰式命名,但这里首字母采用了大写的形式,但在某些特定情况下,例如工厂函数,使用与类名称一致的命名方式是可以接受的,也是有其合理性的.

我们看具体的参数,replay是重放数据的个数,extraBufferCapacity是额外的缓存容量,onBufferOverflow是缓存溢出的策略.

SharedFlow的默认容量和重放个数都是0,因此发射数据时已经订阅的接收者会收到数据,但新注册者不会收到历史数据.

和LiveData的对比:

  • 容量问题: LiveData 容量固定为 1 个,而 SharedFlow 容量支持配置 0 个到 多个;
  • 背压问题: LiveData 无法应对背压问题,而 SharedFlow 有缓存空间能应对背压问题;
  • 重放问题: LiveData 固定重放 1 个数据,而 SharedFlow 支持配置重放 0 个到多个;
  • 线程问题: LiveData 只能在主线程订阅,而 SharedFlow 支持在任意线程(通过协程的 Dispatcher)订阅。

StatedFlow

StateFlow是SharedFlow的子接口,需要传入一个初始值.

  • 有初始值: StateFlow 初始化时必须传入初始值;
  • 容量为 1: StateFlow 只会保存一个值;
  • 重放为 1: StateFlow 会向新订阅者重放最新的值;
  • 不支持 resetReplayCache() 重置重放缓存: StateFlow 的 resetReplayCache() 方法抛出 UnsupportedOperationException
  • 缓存溢出策略为 DROP_OLDEST: 意味着每次发射的新数据会覆盖旧数据;

数据防抖: 意味着仅在更新值并且发生变化才会回调,如果更新值没有变化不会回调 collect,其实就是在发射数据时加了一层拦截:

public override var value: T
    get() = NULL.unbox(_state.value)
    set(value) { updateState(null, value ?: NULL) }
​
override fun compareAndSet(expect: T, update: T): Boolean =
    updateState(expect ?: NULL, update ?: NULL)
​
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 // 如果新值 equals 旧值则拦截, 但 CAS 返回 true
        _state.value = newState
        ...
        return true
    }
}
  • CAS 操作: 原子性的比较与设置操作,只有在旧值与 expect 相同时返回 ture。