初步认识Flow(冷流)
1.Flow上游
a.先看一下flow的简单使用
kotlin
private fun flowTest() = runBlocking {
flow {
emit(0)
emit(1)
emit(2)
emit(3)
emit(4)
}.collect {
Log.d(TAG, "it:$it")
}
}
输出结果:
//输出结果:
//it:0
//it:1
//it:2
//it:3
//it:4
Flow从字面意思理解就是流,Flow除了有发送方和接收方之外还可以有中转站,什么是中转站呢,中转站就是我们常用的中间操作符。比如 : .filter() .map()等等
b.中转站用法
scss
private fun flowTest() = runBlocking {
flow {
//发送方
emit(0)
emit(1)
emit(2)
emit(3)
emit(4)
}.filter {
//中转站(1)
it > 2
}.map {
//中转站(2)
it * 2
}.collect {
//接受方
Log.d(TAG, "it:$it")
}
}
//输出结果:
//it:6
//it:8
(1) flow{}: 是一个高阶函数,作用就是创建一个新的Flow
,创建好后就要把消息发送出去,这里的emit
是发射、发送的意思,那么flow{}
的作用就是创建一个数据流并且将数据发送出去;
(2) filter{}、map{}: 这是中间操作符,都是高阶函数,就像中转站一样对数据进行处理后向下传递;
(3) collect{}: 终止操作符,终止Flow
数据流并接收从上游传递的数据。
(4) 除了通过flow{}
创建Flow
之外还有flowOf{}
,也可以创建一个Flow
kotlin
private fun flowOfTest() {
runBlocking {
flowOf(0, 1, 2, 3, 4)
.filter {
it > 2
}.map {
it * 2
}.collect {
Log.d(TAG, "it:$it")
}
}
}
//输出结果:
//it:6
//it:8
(5)collect ->是终止操作符, 作用是接收从上游传递的数据,那要是不接收会怎么样?
答案是:运行上面的代码会发现什么都没有做就结束了,而添加**collect
函数后filter
和map
的日志就是正常输出的,因此得出一个结论:只有调用终止操作符 collect**之后,Flow 才会开始工作。所以Flow是一个 冷流
没有接受方Flow是不会工作的。
(6)上面两段代码都发送了5条数据,然后由collect
接收,那么是一次发送完毕还是逐条发送呢?
kotlin
private fun flowOfTest() {
runBlocking {
flowOf(0, 1, 2, 3, 4)
.filter {
Log.d(TAG, "filter:$it")
it > 2
}.map {
Log.d(TAG, "map:$it")
it * 2
}.collect {
Log.d(TAG, "collect:$it")
}
}
}
输出结果:
//filter:0
//filter:1
//filter:2
//filter:3
//map:3
//collect:6
//filter:4
//map:4
//collect:8
从输出结果可以很清楚的知道Flow
一次只会处理一条数据。
(7)Kotlin还提供了Flow转List、List转Flow的API....Flow创建的几种方式
Flow创建方式 | 使用场景 | 用法 |
---|---|---|
flow | 未知数据集 | flow{ emit() }.collect{ } |
flowOf | 已知数据集 | flowOf(T).collect{ } |
listOf | 已知数据来源的集合 | listOf(T).asFlow().collect{ } |
2.Flow中转站(中间操作符)
A.中转站就是我们常用的中间操作符,跟集合一样操作符(这边只列出了一部分。更多的自己点击查看源码)
kotlin
/**
* 返回只包含与给定[predicate]匹配的原始流的值的流
*/
public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
if (predicate(value)) return@transform emit(value)
}
/**
* 返回只包含与给定[predicate]值不匹配的原始流的值的流
*/
public inline fun <T> Flow<T>.filterNot(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
if (!predicate(value)) return@transform emit(value)
}
/**
* 返回一个只包含原始流的非空值的流
*/
public fun <T: Any> Flow<T?>.filterNotNull(): Flow<T> = transform<T?, T> { value ->
if (value != null) return@transform emit(value)
}
/**
* 返回一个流,其中包含对原始流的每个值应用给定[transform]函数的结果。
*/
public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
return@transform emit(transform(value))
}
/**
* 返回一个流,将每个元素包装成[IndexedValue],包含value和它的索引(从0开始)。
*/
public fun <T> Flow<T>.withIndex(): Flow<IndexedValue<T>> = flow {
var index = 0
collect { value ->
emit(IndexedValue(checkIndexOverflow(index++), value))
}
}
/**
* 返回一个流,在上游流的每个值被下游发出之前调用给定的[action]。
*/
public fun <T> Flow<T>.onEach(action: suspend (T) -> Unit): Flow<T> = transform { value ->
action(value)
return@transform emit(value)
}
B.Flow
特有的操作符.生命周期onStart, onCompletion
kotlin
private fun flowTest() = runBlocking {
flow {
//发送方
emit(0)
emit(1)
emit(2)
emit(3)
emit(4)
}.filter {
//中转站(1)
Log.d(TAG, "filter:$it")
it > 2
}.map {
Log.d(TAG, "map:$it")
//中转站(2)
it * 2
}.onStart {
Log.d(TAG, "onStart:")
}.onCompletion {
Log.d(TAG, "onCompletion:")
}
.collect {
//接受方
Log.d(TAG, "collect:$it")
}
}
输出:
//onStart:
//filter:0
//filter:1
//filter:2
//filter:3
//map:3
//collect:6
//filter:4
//map:4
//collect:8
//onCompletion
(1)可以看到onStart
函数的执行数序与它在代码中定义的顺序没有关系,而其他两个操作符filter
、map
的执行流程则跟它们定义的顺序息息相关。
(2)onCompletion
在它的注释中也标注的比较清楚,类似于finally
,都是在最后执行。
C. Flow
特有的操作符->catch异常处理
Flow
中的catch
异常处理时要遵循上下游规则的,因为Flow
是具有上下游之分的,具体来讲就是catch
只能管理自己上游发生的异常,对于它下游的异常则无能为力,用代码来展示一下他们的区别:
kotlin
private fun flowOfTest() {
runBlocking {
flowOf(0, 1, 2, 3, 4)
.filter {
Log.d(TAG, "filter:$it")
it > 2
}.map {
Log.d(TAG, "map:$it")
it * 2
}.map {
it / 0
}.catch {
Log.d(TAG, "catch:$it")
}.collect {
Log.d(TAG, "collect:$it")
}
}
}
输出结果:
//filter:0
//filter:1
//filter:2
//filter:3
//map:3
//catch:java.lang.ArithmeticException: divide by zero
上游发生 异常 ,在异常后捕获
kotlin
private fun flowOfTest() {
runBlocking {
flowOf(0, 1, 2, 3, 4)
.filter {
Log.d(TAG, "filter:$it")
it > 2
}.map {
Log.d(TAG, "map:$it")
it * 2
}.catch {
Log.d(TAG, "catch:$it")
}.map {
it / 0
}.collect {
Log.d(TAG, "collect:$it")
}
}
}
//直接奔溃了
// java.lang.ArithmeticException: divide by zero
上游捕获异常,下游发生 异常 。
(1)从两段代码可以非常清楚的总结出:上游发生异常并在异常后捕获是不会造成程序终止的,而在上游捕获异常,下游发生异常时则会造成程序终止。
(2)那么下游的异常就无法捕获了吗?并不是,对于下游的异常可以考虑采用最传统的做法try catch
方法
(3)总结:Flow中的catch操作符的作用与它所在的位置是强相关的,catch无法捕获的可以采用try catch捕获。
D.Flow
特有的操作符------切换Context:flowOn、launchIn
Flow
因为它具有上游、中间操作符、下游的特性,使得它可以处理复杂且异步执行的任务,那么异步执行的任务中大多又涉及到线程切换,Flow
也恰好提供了线程切换的API。
flowOn
scss
private fun flowTest2() {
//flow切换线程
runBlocking {
flow {
emit(0)
emit(1)
emit(2)
emit(3)
emit(4)
}.filter {
Log.d(TAG, "filter:$it")
Log.d(TAG, "thread = ${Thread.currentThread().name}")
it > 2
}.flowOn(Dispatchers.IO).map {
Log.d(TAG, "map:$it")
Log.d(TAG, "thread = ${Thread.currentThread().name}")
it * 2
}.collect {
Log.d(TAG, "collect:$it")
Log.d(TAG, "thread = ${Thread.currentThread().name}")
}
}
}
//输出结果
//filter:0
//thread = DefaultDispatcher-worker-2
//filter:1
//thread = DefaultDispatcher-worker-2
//filter:2
//thread = DefaultDispatcher-worker-2
//filter:3
//thread = DefaultDispatcher-worker-2
//map:3
//map thread = main
//collect: 6
//collect thread = main
//map:4
//map thread = main
//collect: 8
//collect thread = main
flowOn
线程的切换范围与catch
一样仅针对上游,那么要制定collect
中的Context
该怎么办?
可以使用withContext
,如果除了collect
之外还想让其他操作符也运行在collect
所在的线程中就会遇到问题,虽然依旧可以使用withContext
但是这样的写法就会很丑陋,就像下面这样失去了原本简洁的链式调用。那么解决这个问题的另一种方案launchIn
就派上用场了。
launchIn
kotlin
public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
collect()
}
使用了launchIn
操作符的flow
无法再调用collect
,从launchIn
源码中可知,launchIn
调用了collect()
。
scss
private fun flowTest3() {
runBlocking {
flow {
emit(0)
emit(1)
emit(2)
emit(3)
emit(4)
}.filter {
Log.d(TAG, "filter:$it")
Log.d(TAG, "thread = ${Thread.currentThread().name}")
it > 2
}.flowOn(Dispatchers.IO).map {
Log.d(TAG, "map:$it")
Log.d(TAG, "map thread = ${Thread.currentThread().name}")
it * 2
}.onEach {
Log.d(TAG, "onEach:$it")
Log.d(TAG, "onEach thread = ${Thread.currentThread().name}")
}.launchIn(CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
//当然你可以自定义的dispatcher
//lifecycleScope == CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
}
}
//输出结果:
//map{}、onEach{}、flow{}运行在主线程
//filter{}运行在DefaultDispatcher -> DefaultDispatcher-worker-1
3.Flow下游->终止操作符
A. 在Flow
中最常见的操作符就是collect
(),除此之外还有first()
、Last()
、single()
、fold{}
、reduce{}
。还有一个特殊的当Flow
调用toList
转换成集合后toList
后面的API都不再属于Flow
因此这也就说明toList
也算是一种终止操作符
4.Flow总结&特点
Flow的原理
1.Flow流程中为什么是冷流?
我们来分析下flow的源码
kotlin
public fun <T> flow(
@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> =
SafeFlow(block)
private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() {
override suspend fun collectSafely(collector: FlowCollector<T>) {
collector.block()
}
}
flow是一个高阶函数,参数类型是FlowCollector.() -> Unit
,FlowCollector是它的扩展或者成员方法,没有参数也没有返回值,flow()函数的返回值是Flow,具体到返回类型是SafeFlow()
,SafeFlow()
是AbstractFlow()
的子类。
kotlin
public abstract class AbstractFlow<T> : Flow<T>, CancellableFlow<T> {
//注释1
public final override suspend fun collect(collector: FlowCollector<T>) {
//注释2
val safeCollector = SafeCollector(collector, coroutineContext)
try {
//注释3
collectSafely(safeCollector)
} finally {
safeCollector.releaseIntercepted()
}
}
public abstract suspend fun collectSafely(collector: FlowCollector<T>)
}
从AbstractFlow
源码中可以知道它实现了Flow
接口,这里进一步知道了SafeFlow
间接的实现了Flow
接口。这里深入了解一下AbstractFlow
做了哪些工作,通过上面三个注释进行解释:
-
注释1:这个
collect
就是demo中的collect
的调用。这里做一个猜想 :collect
的调用会触发上游Lambda中的emit()
函数的执行,然后将数据传递给collect
; -
注释2:这里主要就是进行了安全检查,
SafeCollector
只是把collect
中的参数FlowCollector<T>
重新进行了封装,具体的安全检查是怎么样的稍后进行分析; -
注释3:这里调用了
collectSafely
这个抽象方法,而这里的具体实现是在SafeFlow
中的collectSafely
,然后调用了collector.block()
,这里其实就是调用了flow()
中的emit
方法,或者说collector.block()
调用了4次emit()
函数。
现在来总结一下为什么Flow
是冷的:FLow之所以是冷的是因为它的构造过程,在它的构造过程中构造了一个 SafeFlow
对象但并不会触发Lambda表达式的执行,只有当 collect()
被调用后Lambda表达式才开始执行所以它是冷的。
2.SafeCollector分析
kotlin
@Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNCHECKED_CAST")
internal actual class SafeCollector<T> actual constructor(
//注释1
@JvmField internal actual val collector: FlowCollector<T>,
@JvmField internal actual val collectContext: CoroutineContext
) : FlowCollector<T>, ContinuationImpl(NoOpContinuation, EmptyCoroutineContext), CoroutineStackFrame {
override val callerFrame: CoroutineStackFrame? get() = completion as? CoroutineStackFrame
override fun getStackTraceElement(): StackTraceElement? = null
@JvmField // Note, it is non-capturing lambda, so no extra allocation during init of SafeCollector
internal actual val collectContextSize = collectContext.fold(0) { count, _ -> count + 1 }
private var lastEmissionContext: CoroutineContext? = null
private var completion: Continuation<Unit>? = null
// ContinuationImpl
override val context: CoroutineContext
get() = completion?.context ?: EmptyCoroutineContext
override fun invokeSuspend(result: Result<Any?>): Any {
result.onFailure { lastEmissionContext = DownstreamExceptionElement(it) }
completion?.resumeWith(result as Result<Unit>)
return COROUTINE_SUSPENDED
}
// Escalate visibility to manually release intercepted continuation
public actual override fun releaseIntercepted() {
super.releaseIntercepted()
}
/**
* 这是状态机重用的巧妙实现。
* 首先它检查它没有被并发使用(这里是明确禁止的),
* 然后只缓存一个完成实例以避免在每次发出时进行额外分配,使其在热路径上消除垃圾。
*/
//注释2
override suspend fun emit(value: T) {
return suspendCoroutineUninterceptedOrReturn sc@{ uCont ->
try {
//注释3
emit(uCont, value)
} catch (e: Throwable) {
//保存已抛出emit异常的事实,便于被上游
lastEmissionContext = DownstreamExceptionElement(e)
throw e
}
}
}
private fun emit(uCont: Continuation<Unit>, value: T): Any? {
val currentContext = uCont.context
currentContext.ensureActive()
//注释4
val previousContext = lastEmissionContext
if (previousContext !== currentContext) {
checkContext(currentContext, previousContext, value)
}
completion = uCont
//注释5
return emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)
}
private fun checkContext(
currentContext: CoroutineContext,
previousContext: CoroutineContext?,
value: T
) {
if (previousContext is DownstreamExceptionElement) {
exceptionTransparencyViolated(previousContext, value)
}
checkContext(currentContext)
lastEmissionContext = currentContext
}
private fun exceptionTransparencyViolated(exception: DownstreamExceptionElement, value: Any?) {
error("""
Flow exception transparency is violated:
Previous 'emit' call has thrown exception ${exception.e}, but then emission attempt of value '$value' has been detected.
Emissions from 'catch' blocks are prohibited in order to avoid unspecified behaviour, 'Flow.catch' operator can be used instead.
For a more detailed explanation, please refer to Flow documentation.
""".trimIndent())
}
}
//注释6
private val emitFun =
FlowCollector<Any?>::emit as Function3<FlowCollector<Any?>, Any?, Continuation<Unit>, Any?>
注释1:这里的collect
参数对应的就是Flow在构造时的匿名内部类FlowCollector
,在AS中看到的this:FlowCollector<Int>
就是它;
注释2:这个emit()
函数其实就是demo中用来发送数据的emit(0)...emit(3)
,这里可以理解为Flow上游发送的数据最终会传递到这个emit()
中;
注释3:这里的emit(uCont, value)
函数中多了两个参数,其中第一个是suspendCoroutineUninterceptedOrReturn
,它是一个高阶函数,是把挂起函数的Continuation
暴露出来并作为参数进行传递,第二个则是上游发送过来的数据。这里还有一个异常捕获,异常被捕获后存储在lastEmissionContext
,作用是:在下游发送异常以后可以让上游感知到。下面会有一个对比;
注释4:这里对当前协程上下文与之前的协程上下文进行对比,如果两者不一致就会执行checkContext()
,在它里面做出进一步的判断和提示;
注释5:这里调用的就是下游的emit()
,也就是FlowCollector
中的emit
函数,这里接收的那个value
就是上游的emit(0)...emit(3)
传递过来的,可以理解成下面demo中的代码:
kotlin
private fun flowOtherTest() {
runBlocking {
flow {
emit(0)
emit(1)
emit(2)
emit(3)
}.collect(object : FlowCollector<Int> {
//注释5调用的就是这个emit方法。
override suspend fun emit(value: Int) {
Log.d(TAG, "collect:$value")
}
})
}
}
注释6:这是函数引用的语法,代表了它就是FlowCollector
的 emit()
方法
3.FlowCollector 上游和下游之间的链接
kotlin
public interface Flow<out T> {
public suspend fun collect(collector: FlowCollector<T>)
}
public fun interface FlowCollector<in T> {
public suspend fun emit(value: T)
}
Flow提供了一个方法,让下游触发一个收集动作collect,理所当然下游作为收集者的FlowCollector也提供了一个emit方法,让下游来接收上游发送数据emit()
在Flow的构造过程中构造了SafeFlow
对象并且间接的实现了Flow,Flow中的collect
就是终止操作,而collect
函数中的参数FlowCollector
中的emit()
函数则是下游用来接收上游发送数据的emit()
。
这里再来回顾一下SafeCollector
方法中所做的事情:首先collect
终止符的调用会触发上游Lambda中的emit()
函数执行,它将数据发送出去,然后进入到SafeCollector
中的emit()
函数,在这个函数中又将从上游数据发送到下游collect
中的emitFun()
函数中,这样就完成了连接。所以说 FlowCollector
是上下游之间的桥梁。(也可以理解为回调)
4.总结
1.Flow的调用过程分为三个步骤:
- 上游的Flow创建
SafeFlow
对象,下游的Flow的collect()
函数触发上游的emit()
函数执行开始发送数据; - 上游的
emit()
发送的数据进入到SafeCollector
,其实上游的emit()
函数调用的就是SafeCollector
中的emit()
函数; - 在
SafeCollector
中调用emitFun()
其实就是调用了下游的emit()
函数将数据传递给下游。
2.中间操作符:
如果有中间操作符的话,每个操作符都会有上游和下游,并且都是被下游触发执行,也会触发自己的上游,同时还会接收来自上游的数据并传递给自己的下游;
ShardFlow与StateFlow(热流)
上面我们介绍到Flow是冷流。那么如果我们要使用热流,该怎么办呢?
答案是Flow 有提供相关实现: 那就是StateFlow 和 SharedFlow 。
StateFlow 和 SharedFlow 是热流,生产数据不依赖消费者消费,热流与消费者是一对多的关系,当有多个消费者时,它们之间的数据都是同一份。
1.SharedFlow与StateFlow的特点
A.SharedFlow
- 对于同一个数据流,可以允许有多个订阅者共享。
- 不调用
collect
收集数据,也会开始发送数据。 - 允许缓存历史数据(默认是非粘性,当设置缓存数据后即可实现为粘性, replay = 1)
- 发送数据函数都是线程安全的。
- 不防抖。可以发送相同的值
- 无初始化值
B.StateFlow
- 都允许多个消费者
- 都有只读与可变类型
- 永远只保存一个状态值,会把最新值重现给订阅者,即粘性。
- 防抖。设置重复的值不会重新发送给订阅者
- 必须传入初始值,保证值的空安全,永远有值
C. StateFlow与LiveData不同的是
- 强制要求初始默认值
- 支持CAS模式赋值
- 默认支持防抖 过滤
- value的空安全校验
Flow
丰富的异步数据流操作- 默认没有
Lifecycle
支持,flow
的collect
是挂起函数,会一直等待数据流传递数据 - 线程安全 ,
LiveData
的postValue
虽然也可在异步使用,但会导致数据丢失。
LiveData除了对于Lifecycle
的支持,StateFlow
基本都是处于优势
2.SharedFlow创建及使用
kotlin
public fun <T> MutableSharedFlow(
//1.缓存的历史数据容量
replay: Int = 0,
//2.除历史数据外的额外缓存区容量
extraBufferCapacity: Int = 0,
//3.背压策略
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
...
val bufferCapacity0 = replay + extraBufferCapacity
val bufferCapacity = if (bufferCapacity0 < 0) Int.MAX_VALUE else bufferCapacity0
return SharedFlowImpl(replay, bufferCapacity, onBufferOverflow)
}
public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
//4.线程安全的挂机函数发送数据
override suspend fun emit(value: T)
//5.线程安全的尝试发送数据
public fun tryEmit(value: T): Boolean
//6.共享数据流的订阅者数量
public val subscriptionCount: StateFlow<Int>
...
}
注释1 : replay 表示历史元素缓存区容量。
- 能够将最新的数据缓存到集合内,当历史缓存区满后,会移除最早的元素。
- 当在新消费者订阅了该数据流,会先将历史缓存区元素依次发送给新的消费者,然后才发送新元素。
注释2:extraBufferCapacity:MutableSharedFlow 缓存的数据个数为 replay + extraBufferCapacity; 缓存一方面用于粘性事件的发送,另一方面也为了处理背压问题,既下游的消费者的消费速度低于上游生产者的生产速度时,数据会被放在缓存中。。
注释3:onBufferOverflow:背压处理策略,缓存区满后怎么处理(挂起或丢弃数据),默认挂起。注意,当没有订阅者时,只有最近 replay 个数的数据会存入缓存区,不会触发 onBufferOverflow 策略。
kotlin
private fun sharedFlow(){
runBlocking {
val _sharedFlow = MutableSharedFlow<Int>()
val sharedFlow = _sharedFlow.asSharedFlow()
lifecycleScope.launch(Dispatchers.IO) {
for(i in 0..50){
Log.d(TAG, "emit:$i")
_sharedFlow.emit(i)
delay(50)
}
}
}
}
输出结果:
//emit:0
//emit:1
// ...
//emit:50
虽然此时并没有消费者订阅,但依旧会执行发送数据操作,只是目前没有设置历史缓存,所有数据都被"抛弃"了。
再让我们看看设置replay = 3的情况。这时候订阅者会收到3个值.
kotlin
private fun sharedFlow(){
runBlocking {
val _sharedFlow = MutableSharedFlow<Int>(replay = 3)
val sharedFlow = _sharedFlow.asSharedFlow()
lifecycleScope.launch(Dispatchers.IO) {
for(i in 0..50){
Log.d(TAG, "emit:$i")
_sharedFlow.emit(i)
delay(50)
}
}
delay(5000)
//延迟5000毫秒去订阅,其实这时候流已经发送完了.相当于新的订阅者
sharedFlow.onEach {
Log.d(TAG, "onEach:$it")
}.launchIn(lifecycleScope)
}
}
输出结果:
//emit:0
//emit:1
// ...
//emit:50
//onEach:48
//onEach:49
//onEach:50
Flow冷流转换成SharedFlow热流
kotlin
private fun shareIn(){
runBlocking {
flowOf(0,1,2,3,4).shareIn(lifecycleScope, SharingStarted.WhileSubscribed())
}
}
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")
val job = scope.launchSharing(config.context, config.upstream, shared, started, NO_VALUE as T)
return ReadonlySharedFlow(shared, job)
}
这里started
表示新创建的共享数据流的启动与停止策略。
- Eagerly->立即开始发送数据源。并且消费端永远收集数据,只会收到历史缓存和后续新数据,直到所在协程取消。
- Lazily->a.等待出现第一个消费者订阅后,才开始发送数据源。保证第一个消费者能收到所有数据,但后续消费者只能收到历史缓存和后续数据。b.消费者会永远等待收集数据,直到所在协程取消
- WhileSubscribed->a.可以说是
Lazily
策略的进阶版,同样是等待第一个消费者订阅后,才开始发送数据源。
b.但其可以配置在最后一个订阅者关闭后,共享数据流上游停止的时间(默认为立即停止),与历史数据缓存清 空时间(默认为永远保留)。
需要注意,在使用shareIn
每次都会创建一个新 SharedFlow
实例 ,并且该实例会一直保留在 内存 中,直到被垃圾回收。所以最好减少转换流的执行次数,不要在函数内每次都调用这类函数。
3.SharedFlow原理
通过MutableSharedFlow
工厂函数创建的SharedFlow
,内部实际是创建了SharedFlowImpl
对象,是使用数组缓存所有数据。
kotlin
internal open class SharedFlowImpl<T>(
private val replay: Int, // replayCache的最大容量
private val bufferCapacity: Int, // buffered values的最大容量
private val onBufferOverflow: BufferOverflow // 溢出策略
) : AbstractSharedFlow<SharedFlowSlot>(), MutableSharedFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
// 缓存数组,用于保存emit方法发射的数据,在需要时进行初始化
private var buffer: Array<Any?>? = null
// 新的订阅者从replayCache中获取数据的起始位置
private var replayIndex = 0L
// 当前所有的订阅者从缓存数组中获取的数据中,对应位置最小的索引
// 如果没有订阅者,则minCollectorIndex的值等于replayIndex
private var minCollectorIndex = 0L
// 缓存数组中buffered values缓存数据的数量
private var bufferSize = 0
// 缓存数组中queued emitters缓存数据的数量
private var queueSize = 0
// 当前缓存数组的起始位置
private val head: Long get() = minOf(minCollectorIndex, replayIndex)
// 当前缓存数组中replayCache缓存数据的数量
private val replaySize: Int get() = (head + bufferSize - replayIndex).toInt()
// 当前缓存数组中已经缓存的数据的数量
private val totalSize: Int get() = bufferSize + queueSize
// 当前缓存数组中buffered values的最末尾位置索引的后一位
private val bufferEndIndex: Long get() = head + bufferSize
// 当前数组中queued emitters的最末尾位置索引的后一位
private val queueEndIndex: Long get() = head + bufferSize + queueSize
...
//2.tryEmit
override fun tryEmit(value: T): Boolean {
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
val emitted = synchronized(this) {
if (tryEmitLocked(value)) {
resumes = findSlotsToResumeLocked(resumes) //寻找需要恢复的挂起函数
true
} else {
false
}
}
for (cont in resumes) cont?.resume(Unit) //恢复执行挂起函数,发送数据
return emitted
}
//1.emit
override suspend fun emit(value: T) {
if (tryEmit(value)) return // fast-path 先尝试发送数据
emitSuspend(value) //无法发送数据时先创建挂起函数
}
}
发送数据
SharedFlowImpl
这个类第一次看起来有点多,这里先从emit
与tryEmit
看起,看看是如何实现发送数据。
在tryEmit
内部通过synchronized
加锁,是线程安全的。
注释1:emit
方法会先进行一次tryEmit
的处理,当返回false的时候再进行suspend的发送操作
注释2:通过前面对tryEmit方法的注释判断缓存策略为 BufferOverflow.SUSPEND
才有可能为true,所以再看tryEmitLocked
方法
kotlin
//从这里可以看到,当策略采用suspended同时缓存溢出的时候,返回false,否则,永远返回true,同时做一些事件上的入队处理等
@Suppress("UNCHECKED_CAST")
private fun tryEmitLocked(value: T): Boolean {
//注释1.--------没有检测到订阅者(collect收集器)
if (nCollectors == 0) return tryEmitNoCollectorsLocked(value) // always returns true
//注释2.--------检测到订阅者(collect收集器)如果当前有订阅者,同时buffered values已达到最大容量。
if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
when (onBufferOverflow) {
BufferOverflow.SUSPEND -> return false // will suspend,只有这里才会返回true
BufferOverflow.DROP_LATEST -> return true // just drop incoming
BufferOverflow.DROP_OLDEST -> {} // force enqueue & drop oldest instead
}
}
//注释3.--------buffered values还可以继续添加数据, 将数据加入到缓存数组中
// 这里因为tryEmit方法不会挂起emit方法所在的协程,
// 所以value没有被封装成Emitter类型的对象
enqueueLocked(value)
bufferSize++ // value was added to buffer
// drop oldest from the buffer if it became more than bufferCapacity
//注释4.-------- 如果buffered values的数据数量超过最大容量的限制,
//则调用dropOldestLocked方法,丢弃最旧的数据
if (bufferSize > bufferCapacity) dropOldestLocked()
// keep replaySize not larger that needed
//注释5.-------- 如果replayCache中数据的数量超过了最大容量
if (replaySize > replay) { // increment replayIndex by one
// 更新replayIndex的值,replayIndex向前移动一位
updateBufferLocked(replayIndex + 1, minCollectorIndex, bufferEndIndex, queueEndIndex)
}
return true
}
- 从
tryEmitLocked
方法代码注释1
处可知: 因为是热流tryEmitNoCollectorsLocked
方法就是没有订阅者的情况
kotlin
private fun tryEmitNoCollectorsLocked(value: T): Boolean {
assert { nCollectors == 0 }
//replay == 0直接返回,不需要去缓存
if (replay == 0) return true // no need to replay, just forget it now
//将值入队
enqueueLocked(value) // enqueue to replayCache
//缓存加1
bufferSize++ // value was added to buffer
// drop oldest from the buffer if it became more than replay
//如果缓存满了,就删除最老的那一条数据
// 如果buffered values的数据数量超过了replayCache的最大容量
// 则丢弃最旧的数据
// 因为新订阅者只会从replayCache中取数据,
// 如果没有订阅者,buffered values的数据数量超过replayCache的最大容量没有意义
if (bufferSize > replay) dropOldestLocked()
//重新设置订阅者的值位置索引
minCollectorIndex = head + bufferSize // a default value (max allowed)
return true
}
// enqueues item to buffer array, caller shall increment either bufferSize or queueSize
private fun enqueueLocked(item: Any?) {
//totalSize = 缓冲值的数目+排队发射器的数量
val curSize = totalSize
val buffer = when (val curBuffer = buffer) {
// 缓存数组为空,则进行初始化,初始化容量为2
null -> growBuffer(null, 0, 2)
// 如果超过了当前缓存数组的最大容量,则进行扩容,新的缓存数组的容量为之前的2倍
// growBuffer方法会把原来缓存数组的数据填充到新的缓存数组中
else -> if (curSize >= curBuffer.size) growBuffer(curBuffer, curSize,curBuffer.size * 2) else curBuffer
}
buffer.setBufferAt(head + curSize, item)
}
从tryEmitLocked方法代码注释2
处可知现在是有订阅者的情况:
1.bufferSize >= bufferCapacity
缓存数组中buffered values缓存数据的数量 >= buffered values的最大容量。
2.minCollectorIndex <= replayIndex
当前所有的订阅者从缓存数组中获取的数据中,对应位置最小的索引<=新的订阅者从replayCache中获取数据的起始位置
到此tryEmitLocked方法分析完了。回到tryEmit方法
kotlin
override fun tryEmit(value: T): Boolean {
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
val emitted = synchronized(this) {
if (tryEmitLocked(value)) {
//收集已经挂起的订阅者的续体
resumes = findSlotsToResumeLocked(resumes)
true
} else {
false
}
}
// 遍历,唤起挂起的订阅者
for (cont in resumes) cont?.resume(Unit)
return emitted
}
findSlotsToResumeLocked
kotlin
private fun findSlotsToResumeLocked(resumesIn: Array<Continuation<Unit>?>): Array<Continuation<Unit>?> {
// 引用参数中的续体数组
var resumes: Array<Continuation<Unit>?> = resumesIn
// 用于记录需要恢复的续体的数量
var resumeCount = resumesIn.size
// 遍历订阅者数组
forEachSlotLocked loop@{ slot ->
// 获取续体,如果续体为空,说明对应订阅者的协程没有挂起,本次循环返回
val cont = slot.cont ?: return@loop
// 判断slot中index是否符合要求
// 如果不符合要求,则本次循环返回
if (tryPeekLocked(slot) < 0) return@loop
// 如果需要恢复的续体的数量超过续体数组的容量,则进行扩容
// 新的续体数组的容量是之前续体数组容量的2倍
if (resumeCount >= resumes.size) resumes = resumes.copyOf(maxOf(2, 2 * resumes.size))
// 保存续体到续体数组中
resumes[resumeCount++] = cont
// 清空slot中保存的续体
slot.cont = null
}
// 返回收集完的续体数组
return resumes
}
到这里非 挂起 的发送流程结束,下面看看挂起发送流程
kotlin
override suspend fun emit(value: T) {
//非挂起
if (tryEmit(value)) return // fast-path
//挂起
emitSuspend(value)
}
挂起调用emitSuspend
方法,这里也调用了上面提到的findSlotsToResumeLocked
方法
kotlin
private suspend fun emitSuspend(value: T) =
// 直接挂起emit方法所在的协程,获取续体
suspendCancellableCoroutine<Unit> sc@{ cont ->
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
// 加锁
val emitter = synchronized(this) lock@{
// 这里再次尝试以tryEmit的方式发射数据
if (tryEmitLocked(value)) {
// 如果发射成功,则恢复续体的执行
cont.resume(Unit)
// 收集已经挂起的订阅者的续体
resumes = findSlotsToResumeLocked(resumes)
// 返回
return@lock null
}
// 将续体、待发射的数据等封装成Emitter类型的对象
Emitter(this, head + totalSize, value, cont).also {
// 加入到缓存数组中
enqueueLocked(it)
// queued emitters的数据的数量加1
queueSize++
// 如果buffered values的最大容量为0,即不存在
// 则收集已经挂起的订阅者的续体,保存到局部变量resumes中
if (bufferCapacity == 0) resumes = findSlotsToResumeLocked(resumes)
}
}
// emitter对象监听emit方法所在协程的取消
// 发生取消时会调用emitter对象的dispose方法
emitter?.let { cont.disposeOnCancellation(it) }
// 遍历,唤起挂起的订阅者
for (cont in resumes) cont?.resume(Unit)
}
sharedFlow设置了缓存优先从replayCache
取数据.SharedFlowImpl类实现了SharedFlow接口,重写了其中的常量replayCache
,当有新订阅者出现时,如果replayCache
存在,并且有缓存数据,则优先从replayCache中获取,代码如下:
kotlin
override val replayCache: List<T>
// 只能获取,不能设置,加锁
get() = synchronized(this) {
// 获取当前replayCache中缓存数据的数量
val replaySize = this.replaySize
// 如果数量为0,则返回一个空列表
if (replaySize == 0) return emptyList()
// 若数量不为0,则根据容量创建一个列表
val result = ArrayList<T>(replaySize)
// 获取缓存数组
val buffer = buffer!!
// 遍历replayCache,将数据进行类型转换,并添加到列表中
@Suppress("UNCHECKED_CAST")
for (i in 0 until replaySize) result += buffer.getBufferAt(replayIndex + i) as T
// 返回列表
result
}
collect流程(接受数据)
kotlin
SharedFlowImpl.kt
@Suppress("UNCHECKED_CAST")
override suspend fun collect(collector: FlowCollector<T>) {
// 为当前的订阅者分配一个SharedFlowSlot类型的对象
val slot = allocateSlot()
try {
// 如果collector类型为SubscribedFlowCollector,
// 说明订阅者监听了订阅过程的启动,则先回调
if (collector is SubscribedFlowCollector) collector.onSubscription()
// 获取订阅者所在的协程
val collectorJob = currentCoroutineContext()[Job]
// 死循环
while (true) {
var newValue: Any?
// 死循环
while (true) {
// 从缓存数组中获取数据
newValue = tryTakeValue(slot)
// 如果获取数据成功,则跳出循环
if (newValue !== NO_VALUE) break
// 走到这里,说明获取数据失败,
// 挂起订阅者所在协程,等待新数据的到来
awaitValue(slot)
}
// 走到这里,说明已经获取到了数据
// 判断订阅者所在协程是否是存活的,如果不是则抛出异常
collectorJob?.ensureActive()
// 进行类型转换,并向下游发射数据
collector.emit(newValue as T)
}
} finally {
// 释放已分配的SharedFlowSlot类型的对象
freeSlot(slot)
}
}
@SharedImmutable
@JvmField
internal val NO_VALUE = Symbol("NO_VALUE")
在collect方法中,通过tryTakeValue方法获取数据,代码如下:
kotlin
private fun tryTakeValue(slot: SharedFlowSlot): Any? {
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
// 加锁
val value = synchronized(this) {
// 从slot中获取index
// index表示当前应该从缓存数组的index位置中获取数据
val index = tryPeekLocked(slot)
// 如果index小于0,说明没有数据
if (index < 0) {
// 返回空数据标识
NO_VALUE
} else { // 如果有数据
// 获取当前的slot的index
val oldIndex = slot.index
// 从缓存数组的index处获取数据
val newValue = getPeekedValueLockedAt(index)
// 计算下一次获取数据的位置,并保存到slot中
slot.index = index + 1
// 更新缓存数组的位置,并获取缓存数组与订阅者数组中可恢复的续体
resumes = updateCollectorIndexLocked(oldIndex)
// 返回获取的数据
newValue
}
}
// 遍历,恢复续体
for (resume in resumes) resume?.resume(Unit)
// 返回获取的数据
return value
}
@JvmField
@SharedImmutable
internal val EMPTY_RESUMES = arrayOfNulls<Continuation<Unit>?>(0)
在tryTakeValue方法,获取数据之前,首先会调用tryPeekLocked方法,判断数据所在的位置是否符合要求,代码如下:
kotlin
private fun tryPeekLocked(slot: SharedFlowSlot): Long {
// 从slot中获取index
val index = slot.index
// 如果是在buffered values中获取,则直接返回
if (index < bufferEndIndex) return index
// 走到这里说明是要在queued emitters中获取,
// 如果buffered values的最大容量大于0,则返回-1
// 在buffered values可以存在的情况下,禁止发射者和订阅者接触
if (bufferCapacity > 0) return -1L
// 走到这里说明要在queued emitters中获取,同时buffered values的最大容量为0
// 这种情况缓存数组只能有queued emitters,
// 因此,只能处理queued emitters中的第一个Emitter类型的对象
// 如果当前订阅者想要处理下一个Emitter类型的对象,则返回-1
if (index > head) return -1L
// 走到这里说明要在queued emitters中获取,同时buffered values的最大容量为0
// 并且要获取当前的正在处理的Emmiter类型的对象
// 如果queued emitters为空,说明当前没有Emmiter类型的对象,则返回-1
if (queueSize == 0) return -1L
// 满足上述要求,返回index
return index
}
如果数据所在的位置符合要求,则会调用getPeekedValueLockedAt方法获取数据,代码如下:
kotlin
private fun getPeekedValueLockedAt(index: Long): Any? =
// 从缓存数组中index位置获取数据
when (val item = buffer!!.getBufferAt(index)) {
// 如果是Emitter类型的,则进行拆箱,获取数据
is Emitter -> item.value
// 直接返回
else -> item
}
Emitter类是SharedFlowImpl类的内部类,用于在挂起调用emit方法所在的协程后,对emit方法发射的数据及挂起后的续体进行封装,代码如下:
less
private class Emitter(
@JvmField val flow: SharedFlowImpl<*>,
@JvmField var index: Long, // 当前对象在缓存数组中的位置
@JvmField val value: Any?,// emit方法发射的数据
@JvmField val cont: Continuation<Unit> // 挂起的续体
) : DisposableHandle {
override fun dispose() = flow.cancelEmitter(this)
}
在collect方法中,当订阅者无数据可获取时,则会调用awaitValue方法,挂起订阅者所在的协程,代码如下:
kotlin
private suspend fun awaitValue(slot: SharedFlowSlot): Unit =
// 直接挂起订阅者所在的协程
suspendCancellableCoroutine { cont ->
// 加锁
synchronized(this) lock@{
// 再次检查当前的index是否满足要求
val index = tryPeekLocked(slot)
// 如果确实不满足要求
if (index < 0) {
// 保存续体到slot中
slot.cont = cont
} else { // 如果再次检查发现index这时满足要求
// 则恢复挂起,并返回
cont.resume(Unit)
return@lock
}
// 保存续体到slot中
slot.cont = cont
}
}
到此SharedFlow就分析完成了。
我们知道SharedFlow内部是基于数组+synchronized,订阅分发的逻辑则是轮询取出内部缓存数组的数据,没有数据则将订阅挂起,直到上游再次发送数据
StateFlow创建及使用
作为SharedFlow
的子类,StateFlow
在使用上与其父类基本相同。
kotlin
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
public override var value: T
public fun compareAndSet(expect: T, update: T): Boolean
}
StateFlow是一种单数据更新的热流,通过emit方法更新StateFlow的数据,通过value属性可以获取当前的数据
上面同样是利用同名工厂函数的进行创建,只是相比SharedFlow,StateFlow必须设置默认初始值。
scss
private fun stateFlow(){
runBlocking {
val _stateFlow = MutableStateFlow(3)
val stateFlow = _stateFlow.asStateFlow()
//没有发送数据时立即订阅
stateFlow.onEach {
Log.d(TAG, "onEach:$it")
}.launchIn(lifecycleScope)
//模拟发送数据
lifecycleScope.launch(Dispatchers.IO) {
for(i in 3..50){
Log.d(TAG, "emit:$i")
_stateFlow.emit(i)
delay(50)
}
}
//延迟5000毫秒去订阅,其实这时候流已经发送完了.相当于新的订阅者
delay(5000)
//新的订阅者
stateFlow.onEach {
Log.d(TAG, "new onEach:$it")
}.launchIn(lifecycleScope)
}
}
//输出结果:
//onEach:3
//emit:3
//emit:4
//onEach:4
//emit:5
//onEach:5
//...
//新的订阅者直接输出
//new onEach:50
从上面的输出结果看出:
-
在没有发送数据时订阅 ,会先接收默认值。
-
而新发送的数据后,由于第一个值与原有值相同,直接被过滤掉了。在StateFlow中,通过Any#equals方法来判断前后两个数据是否相等。当前后两个数据相等时,数据不会被更新,订阅者也不会处理。
-
StateFlow必须要有一个初始值。当新订阅者出现时,StateFlow会将最新的数据发射给订阅者。StateFlow只保留最后发射的数据,除此之外不会缓存任何其他的数据。同时,StateFlow不支持resetReplayCache方法。
Flow 冷流 转换成StateFlow热流
StateFlow同样也有由Flow冷流转化为热流的操作符函数stateIn。
kotlin
private fun stateIn(){
flowOf(0,1,2,3,4).stateIn(lifecycleScope, SharingStarted.WhileSubscribed(),0)
}
与shareIn
函数的区别只是必须设置默认值 ,stateIn
转化的共享数据流只缓存一个最新值。这里不多说
StateFlow的原理
1.订阅者的管理类->StateFlowSlot
上面分析SharedFlow原理分析中没有单独列出来说SharedFlowSlot是SharedFlowImpl的订阅者的管理类。这里说明一下:SharedFlowImpl类中,订阅者数组中存储的对象类型为SharedFlowSlot,而在StateFlowImpl类中,订阅者数组存储的对象类型为StateFlowSlot。
less
@SharedImmutable
private val NONE = Symbol("NONE") // 状态标识
@SharedImmutable
private val PENDING = Symbol("PENDING") // 状态标识
private class StateFlowSlot : AbstractSharedFlowSlot<StateFlowImpl<*>>() {
// _state中默认值为null
private val _state = atomic<Any?>(null)
...
}
根据_state保存对象的不同,可以确定StateFlowSlot类型的对象的状态。StateFlowSlot类型的对象共有四种状态:
- null:如果_state保存的对象为空,表示当前StateFlowSlot类型的对象没有被任何订阅者使用。
- NONE:如果_state保存的对象为NONE标识,表示当前StateFlowSlot类型的对象已经被对应的订阅者使用,但既没有挂起,也没有在处理当前的数据。
- PENDING:如果_state保存的对象为PENDING标识,表示当前StateFlowSlot类型的对象已经被对应的订阅者使用,并且将开始处理当前的数据。
- CancellableContinuationImpl :如果_state保存的对象为续体,表示当前StateFlowSlot类型的对象已经被对应的订阅者使用,但是订阅者已处理完当前的数据,所在的协程已被挂起,等待新的数据到来。
在StateFlowSlot类中,重写了AbstractSharedFlowSlot类的allocateLocked方法与freeLocked方法,两个方法会对订阅者的初始状态和最终状态进行改变,代码如下:
kotlin
// 新订阅者申请使用当前的StateFlowSlot类型的对象
override fun allocateLocked(flow: StateFlowImpl<*>): Boolean {
// 如果_state保存的对象不为空,
// 说明当前StateFlowSlot类型的对象已经被其他订阅者使用
// 返回false
if (_state.value != null) return false
// 走到这里,说明没有被其他订阅者使用,分配成功
// 修改状态值为NONE
_state.value = NONE
// 返回true
return true
}
// 订阅者释放已经使用的StateFlowSlot类型的对象
override fun freeLocked(flow: StateFlowImpl<*>): Array<Continuation<Unit>?> {
// 修改状态值为null
_state.value = null
// 返回空数组
return EMPTY_RESUMES
}
@JvmField
@SharedImmutable
internal val EMPTY_RESUMES = arrayOfNulls<Continuation<Unit>?>(0)
为了实现上述对订阅者状态的管理,在StateFlowSlot类中,还额外提供了三个方法用于实现对订阅者的状态的切换,代码如下:
kotlin
// 当有状态更新成功时,会调用makePending方法,通知订阅者可以开始处理新数据
@Suppress("UNCHECKED_CAST")
fun makePending() {
// 根据当前状态判断
_state.loop { state ->
when {
// 如果未被订阅者使用,则直接返回
state == null -> return
// 如果已经处于PENDING状态,则直接返回
state === PENDING -> return
// 如果当前状态为NONE
state === NONE -> {
// 通过CAS的方式,将状态修改为PENDPENDING,并返回
if (_state.compareAndSet(state, PENDING)) return
}
// 如果为挂起状态
else -> {
// 通过CAS的方法,将状态修改为NONE
if (_state.compareAndSet(state, NONE)) {
// 如果修改成功,则恢复对应续体的执行,并返回
(state as CancellableContinuationImpl<Unit>).resume(Unit)
return
}
}
}
}
}
// 当订阅者每次处理完新数据(不一定处理成功)后,会调用takePending方法,表示完成处理
// 获取当前的状态,并修改新状态为NONE
fun takePending(): Boolean = _state.getAndSet(NONE)!!.let { state ->
assert { state !is CancellableContinuationImpl<*> }
// 如果之前的状态为PENDING,则返回true
return state === PENDING
}
// 当订阅者没有新数据需要处理时,会调用awaitPending方法挂起
@Suppress("UNCHECKED_CAST")
// 直接挂起,获取续体
suspend fun awaitPending(): Unit = suspendCancellableCoroutine sc@ { cont ->
assert { _state.value !is CancellableContinuationImpl<*> }
// 通过CAS的方式,将当前的状态修改为挂起,并返回
if (_state.compareAndSet(NONE, cont)) return@sc
// 走到这里代表状态修改失败,说明又发射了新数据,当前的状态被修改为PENDING
assert { _state.value === PENDING }
// 唤起订阅者续体的执行
cont.resume(Unit)
}
发送数据
在StateFlowImpl类中,当需要发射数据时,可以调用emit方法、tryEmit方法、compareAndSet方法,代码如下:
kotlin
override fun tryEmit(value: T): Boolean {
this.value = value
return true
}
override suspend fun emit(value: T) {
this.value = value
}
override fun compareAndSet(expect: T, update: T): Boolean =
updateState(expect ?: NULL, update ?: NULL)
@Suppress("UNCHECKED_CAST")
public override var value: T
// 拆箱
get() = NULL.unbox(_state.value)
// 更新数据
set(value) { updateState(null, value ?: NULL) }
// 拆箱操作
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
inline fun <T> unbox(value: Any?): T = if (value === this) null as T else value as T
可以发现,无论是通过emit方法、tryEmit方法还是compareAndSet方法,最终都是通过updateState方法实现数据的更新,代码如下:
kotlin
// sequence是一个全局变量,当新的数据更新时,sequence会发生变化
// 当sequence为奇数时,表示当前数据正在更新
private var sequence = 0
// CAS方式更新当前数据的值
private fun updateState(expectedState: Any?, newState: Any): Boolean {
var curSequence = 0
// 获取所有的订阅者
var curSlots: Array<StateFlowSlot?>? = this.slots
// 加锁
synchronized(this) {
// 获取当前数据的值
val oldState = _state.value
// 如果期待数据不为空,同时当前数据不等于期待数据,则返回false
if (expectedState != null && oldState != expectedState) return false
// 如果新数据与老数据相同,即前后数据没有发生变化,则直接返回true
if (oldState == newState) return true
// 更新当前数据
_state.value = newState
// 获取全局变量
curSequence = sequence
// 如果为偶数,说明updateState方法没有被其他协程调用,没有并发
if (curSequence and 1 == 0) {
// 自增加1,表示当前正在更新数据
curSequence++
// 将新值保存到全局变量中
sequence = curSequence
} else { // 如果为奇数,说明updateState方法正在被其他协程调用,处于并发中
// 加2后不改变奇偶性,只是表示当前数据发生了变化
sequence = curSequence + 2
// 返回true
return true
}
// 获取当前所有的订阅者
curSlots = slots
}
// 走到这里,说明上面不是并发调用updateState方法的情况
// 循环,通知订阅者
while (true) {
// 遍历,修改订阅者的状态,通知订阅者
curSlots?.forEach {
it?.makePending()
}
// 加锁,判断在通知订阅者的过程中,数据是否又被更新了
synchronized(this) {
// 如果数据没有被更新
if (sequence == curSequence) {
// 加1,让sequence变成偶数,表示更新完毕
sequence = curSequence + 1
// 返回true
return true
}
// 如果数据有被更新,则获取sequence和订阅者
// 再次循环
curSequence = sequence
curSlots = slots
}
}
}
接受数据
调用StateFlow类型对象的collect方法,会触发订阅过程,接收emit方法发送的数据,这部分在 StateFlowImpl中实现,代码如下:
kotlin
override suspend fun collect(collector: FlowCollector<T>) {
// 为当前的订阅者分配一个StateFlowSlot类型的对象
val slot = allocateSlot()
try {
// 如果collector类型为SubscribedFlowCollector,
// 说明订阅者监听了订阅过程的启动,则先回调
if (collector is SubscribedFlowCollector) collector.onSubscription()
// 获取订阅者所在的协程
val collectorJob = currentCoroutineContext()[Job]
// 局部变量,保存上一次发射的数据,初始值为null
var oldState: Any? = null
// 死循环
while (true) {
// 获取当前的数据
val newState = _state.value
// 判断订阅者所在协程是否是存活的,如果不是则抛出异常
collectorJob?.ensureActive()
// 如果订阅者是第一次处理数据或者当前数据与上一次数据不同
if (oldState == null || oldState != newState) {
// 将数据发送给下游
collector.emit(NULL.unbox(newState))
// 保存当前发射数据到局部变量
oldState = newState
}
// 修改状态,如果之前不是PENGDING状态
if (!slot.takePending()) {
// 则挂起等待新数据更新
slot.awaitPending()
}
}
} finally {
// 释放已分配的StateFlowSlot类型的对象
freeSlot(slot)
}
}
新订阅者怎么获取获取缓存数据呢
当新订阅者出现时,StateFlow会将当前最新的数据发送给订阅者。可以通过调用StateFlowImpl类重写的常量replayCache获取当前最新的数据,代码如下:
csharp
override val replayCache: List<T>
get() = listOf(value)
看到这里就能明白为什么新订阅者能拿到数据。因为每次都是获取最新的value..而且还不能清楚缓存。如果清楚就会报错
kotlin
override fun resetReplayCache() {
throw UnsupportedOperationException("MutableStateFlow.resetReplayCache is not supported")
}
到此我们常用的sharedFlow和stateFlow就分析完了。。。。
当然我这里分析sharedFlow和stateFlow里面的原理还是没有分析很仔细。只是根据注释和流程对它们的有所了解。后续如果我们开发起来遇到问题就能很快去翻看源码找到答案。。用起来也能很随心应手~~~