前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >了解 Kotlin Flow(一)

了解 Kotlin Flow(一)

作者头像
烧麦程
发布2022-05-10 20:50:40
9750
发布2022-05-10 20:50:40
举报
文章被收录于专栏:半行代码

最近了解了一下 Koltin Flow 相关的一些内容。在这里做一些简单的总结。关于 Flow 的知识点有如下一些:

  • Flow 的基本使用
  • StateFlow 和 SharedFlow 的使用和基本原理
  • StateFlow、SharedFlow 在 Android 中使用的时候和 LiveData 的比较

基础使用

Kotlin 里 Flow 的基本用法是使用一个 flow 方法创建 Flow 对象:

代码语言:javascript
复制
flow {}

需要更新值的时候,在代码块内使用 emit 方法发射值。需要监听值的时候,使用 collect 方法。

flow 方法会创建一个 SafeFlow 对象。

SafeFlow 的继承关系如上图。我们看下 SafeFlow 是如何收集值的

代码语言:javascript
复制
// SafeFlow collect
public final override suspend fun collect(collector: FlowCollector<T>) {
   val safeCollector = SafeCollector(collector, coroutineContext)
    try {
      collectSafely(safeCollector)
    } finally {
      safeCollector.releaseIntercepted()
    }
}

这里创建一个 SafeCollector,并执行 collectSafely:

代码语言:javascript
复制
// SafeFlow collectSafely
override suspend fun collectSafely(collector: FlowCollector<T>) {
  collector.block()
}

这里很简单,就是执行了一下 SafeCollector 的 block,也就是我们使用 flow 时候的代码块,它其实是 FlowCollector 的一个扩展方法。所以这里调用 emit 的时候调用的也就是 SafeCollectoremit。所以 flow 的代码块里的逻辑是需要 collect 之后才会去执行的,我们管这种叫做 冷流

再看下这里是怎么发射值的:

代码语言:javascript
复制
// SafeCollector emit
fun emit(uCont: Continuation<Unit>, value: T): Any? {
  val currentContext = uCont.context
 currentContext.ensureActive()
 // This check is triggered once per flow on happy path.
 val previousContext = lastEmissionContext
 if (previousContext !== currentContext) {
  checkContext(currentContext, previousContext, value)
 }
 completion = uCont
 return emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)
}

// checkContext
private fun checkContext(currentContext: CoroutineContext,previousContext: CoroutineContext?,value: T) {
 if (previousContext is DownstreamExceptionElement) {
  exceptionTransparencyViolated(previousContext, value)
 }
 checkContext(currentContext)
 lastEmissionContext = currentContext
}

这里会检查一下协程上下文,然后执行 emitFun上下文检查会检查如下内容,这部分在相关源码的注释里面有写,读者感兴趣可以去翻阅一下。

  1. 检查是否存在 DownstreamExceptionElement。如果下游存在异常,就会包装成 DownstreamExceptionElement ,这样上游 emit 的时候就会知道,这时候就会抛出异常。我们看一段源码注释里的示例
代码语言:javascript
复制
val flow = flow {
 emit(1)
 try {
  emit(2)
 } catch (e:Exception) {
  emit(3)
 }
}

flow.collect {
 if (it == 2) {
  throw CancellationException("cancel")
 } else {
  println("collect $it")
 }
}

这段代码的输出为

代码语言:javascript
复制
collect 1
cancel

这里当collect到2的时候下游抛出了一个异常,从预期上来讲,数据流出现异常应该终止,如果没有上面的这个机制,实际上的输出会变成

代码语言:javascript
复制
collect 1
collect 3

如果不对上下文进行检查,那么产生的结果和我们的预期是不符的。

  1. 检查 Flow 的不可变性。因为 emit 不是线程安全的,所以我们不允许使用的时候出现下面这种情况:
代码语言:javascript
复制
coroutineScope {
 launch {
   emit(1)
  }
  
  launch {
    emit(2)
  }
}

这种情况会在 checkContext 里面调用 transitiveCoroutineParent 进行判断:

代码语言:javascript
复制
// SafeCollector.common.kt
internal tailrec fun Job?.transitiveCoroutineParent(collectJob: Job?): Job? {
  if (this === null) return null
  if (this === collectJob) return this
  if (this !is ScopeCoroutine<*>) return this
  return parent.transitiveCoroutineParent(collectJob)
}

如果 emit 所在上下文和 collect 所在上下文不一致,就会报错。

在日常开发中,普通的 Flow API 只能有一个 collect 的地方,并不能满足我们的需求,所以 Kotlin 还提供了更丰富的 APIStateFlowSharedFlow

StateFlow

顾名思义,StateFlow 就是维护状态的 Flow, 它的使用非常类似 LiveData

代码语言:javascript
复制
val state = MutableStateFlow<Int>(0)//必须要初始值
// 更新值
state.value = 100
// 监听
state.collect{} // in coroutine suspend method 

MutableStatFlow 支持多个观察者对其进行 collect,并且 MutableStateFlow 初始化的时候必须有值。这个和 Livedata 非常接近,也会存在 LiveData 的粘性事件的问题。

SharedFlow

SharedFlow 的使用类似 StateFlow:

代码语言:javascript
复制
val stream = MutableSharedFlow()
// 更新
scope.launch {
 stream.emit(100)
}  
//监听
scope.launch {
 stream.collect {}
}

StateFlow相比,SharedFlow 只有 emit 方法,并且构造方法里面有 3 个可选的参数。

  • replay
  • extraBufferCapacity
  • onBufferOverflow

replay 和 extraBufferCapacity 默认都为 0, replay 为重播给收集方的数量。例如数据流依次为 1, 2, 3, 4, 5,replay 是2,那么这时候收集方会收到 4, 5 的值。extraBufferCapacity 为额外的缓冲队列的容量。onBufferOverrflow 则为背压(collect比emit耗时,emit的数据源太多处理不过来)的情况下的处理策略,包括

  1. SUSPEND 挂起
  2. DROP_OLDEST 把之前的未处理任务丢弃
  3. DROP_LATEST 把新的任务丢弃

从这里可以看出, SharedFlow 非常适合作为事件流的处理。当参数都为默认值的时候,重播数量为0,那么只会在 emit 最新值的时候才会收到通知。也不会存在粘性事件的问题。

总结

初步认识上面的知识点后,我们可以利用 Flow 来改进之前的 Android 代码:

  • UI 相关的状态我们仍然可以使用 LiveData 或者直接使用 StateFlow ,需要注意的是,状态 是一直存在的 UI 状态,

无论页面重建或者位置改变,状态都是存在的。这里的状态是允许重复响应的,例如一个 TextView 显示的值是 200,那么即使 页面发生了重建,这个值仍然显示的是 200。

  • 一些依赖变化的逻辑状态,我们可以理解为事件,使用 SharedFlow 替代 LiveData 进行处理。场景的逻辑状态有例如 加载成功后弹一个 Toast,图片裁剪后触发上传操作等。

这类逻辑如果使用 LiveData 或者 StateFlow,当页面重建后,之前的值都会被监听到,反复弹 Toast,这是一件非常麻烦的事情, 如果使用 SharedFlow ,则不会遇到。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 半行代码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基础使用
  • StateFlow
  • SharedFlow
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档