![[一看就懂] 图解 Kotlin SharedFlow 缓存系统](http://pic.xiahunao.cn/yaotu/[一看就懂] 图解 Kotlin SharedFlow 缓存系统)
作者fundroid链接https://juejin.cn/post/7156408785886511111前言Kotlin 为我们提供了两种创建“热流”的工具StateFlow和SharedFlow。StateFlow 经常被用来替代 LiveData 充当架构组件使用所以大家相对熟悉。其实 StateFlow 只是 SharedFlow 的一种特化形式SharedFlow 的功能更强大、使用场景更多这得益于其自带的缓存系统本文用图解的方式带大家更形象地理解 SharedFlow 的缓存系统。创建 SharedFlow 需要使用到MutableSharedFlow()方法我们通过方法的三个参数配置缓存fun T MutableSharedFlow( replay: Int 0, extraBufferCapacity: Int 0, onBufferOverflow: BufferOverflow BufferOverflow.SUSPEND ): MutableSharedFlowT 复制代码接下来我们通过时序图的形式介绍这三个关键参数对缓存的影响。正文之前让我们先统一一下用语EmitterFlow 数据的生产者从上游发射数据SubcriberFlow 数据的消费者在下游接收数据replay当 Subscriber 订阅 SharedFlow 时有机会接收到之前已发送过的数据replay 指定了可以收到 subscribe 之前数据的数量。replay 不能为负数默认值为 0 表示 Subscriber 只能接收到 subscribe 之后 emit 的数据上图展示的是 replay 0 的情况Subscriber 无法收到 subscribe 之前 emit 的 ❶只能接收到 ❷ 和 ❸。当 replay n n 0时SharedFlow 会启用缓存此时 BufferSize 为 n意味着可以缓存发射过的最近 n 个数据并发送给新增的 Subscriber。上图以 n 1 为例 Emitter 发送 ❶ 并被 Buffer 缓存Subscriber 订阅 SharedFlow 后接收到缓存的 ❶Emitter 相继发送 ❷ ❸ Buffer 缓存的数据相继依次被更新在生产者消费者模型中有时消费的速度赶不及生产此时要加以控制要么停止生产要么丢弃数据。SharedFlow 也同样如此。有时 Subscriber 的处理速度较慢Buffer 缓存的数据得不到及时处理当 Buffer 为空时emit 默认将会被挂起 onBufferOverflow SUSPEND上面的图展示了 replay 1 时 emit 发生 suspend 场景Emitter 发送 ❶ 并被缓存Subscriber 订阅 SharedFlow 接收 replay 的 ❶ 开始处理Emitter 发送 ❷ 缓存数据更新为 ❷ 由于 Subscriber 对 ❶ 的处理尚未结束❷ 在缓存中没有及时被消费Emitter 发送 ❸由于缓存的 ❷ 尚未被 Subscriber 消费emit 发生挂起Subscriber 开始消费 ❷ Buffer 缓存 ❸ Emitter 可以继续 emit 新数据注意 SharedFlow 作为一个多播可以有多个 Subscriber所以上面例子中❷ 被消费的时间点取决于最后一个开始处理的 Subscriber。extraBufferCapacityextraBufferCapacity 中的 extra 表示 replay-cache 之外为 Buffer 还可以额外追加的缓存。若 replay n extraBufferCapacity m则 BufferSize m n。extraBufferCapacity 默认为 0设置 extraBufferCapacity 有助于提升 Emitter 的吞吐量在上图的基础之上我们再设置 extraBufferCapacity 1效果如下图上图中 BufferSize 1 1 2 Emitter 发送 ❶ 并得到 Subscriber1 的处理 ❶ 作为 replay 的一个数据被缓存Emitter 发送 ❷Buffer 中 replay-cache 的数据更新为 ❷Emitter 发送 ❸Buffer 在存储了 replay 数据 ❷ 之上作为 extra 又存储了 ❸Emitter 发送 ❹此时 Buffer 已没有空余位置emit 挂起Subscriber2 订阅 SharedFlow。虽然此时 Buffer 中存有 ❷ ❸ 两个数据但是由于 replay 1所以 Subscriber2 只能收到最近的一个数据 ❸Subscriber1 处理完 ❶ 后依次处理 Buffer 中的下一个数据开始消费 ❷对于 SharedFlow 来说已经不存在没有消费 ❷ 的 Subscriber❷ 移除缓存❹ 的 emit 继续并进入缓存此时 Buffer 又有两个数据 ❸ ❹ Subscriber1 处理完 ❷ 开始消费 ❸不存在没有消费 ❸ 的 Subscriber ❸ 移除缓存。onBufferOverflow前面的例子中当 Buffer 被填满时emit 会被挂起这都是建立在 onBufferOverflow 为 SUSPEND 的前提下的。onBufferOverflow 用来指定缓存移除时的策略除了默认的 SUSPEND还有两个数据丢弃策略DROP_LATEST丢弃最新的数据DROP_OLDEST丢弃最老的数据需要特别注意的是当 BufferSize 0 时extraBufferCapacity 只支持 SUSPEND其他丢弃策略是无效的。这很好理解因为 Buffer 中没有数据所以丢弃无从下手所以启动丢弃策略的前提是 Buffer 至少有一个缓冲区且数据被填满上图展示 DROP_LATEST 的效果。假设 replay 2extra 0Emitter 发送 ❸ 时由于 ❶ 已经被消费所以 Buffer 数据从 ❶❷ 变为 ❷❸Emitter 发送 ❹ 时由于 ❷ 还未被消费Buffer 处于填满状态 ❹ 直接被丢弃Emitter 发送 ❺ 时由于 ❷ 已经被费可以移除缓存Buffer 数据变为 ❸❺上图展示了 DROP_OLDEST 的效果与 DROP_LATEST 比较后非常明显缓存中永远会储存最新的两个数据但是较老的数据不管有没有被消费都可能会从 Buffer 移除所以 Subscriber 可以消费当前最新的数据但是有可能漏掉中间的数据比如图中漏掉了 ❷注意当 extraBufferCapacity 设为 SUSPEND 可以保证 Subscriber 一个不漏的消费掉所有数据但是会影响 Emitter 的速度当设置为 DROP_XXX 时可以保证 emit 调用后立即返回但是 Subscriber 可能会漏掉部分数据。如果我们不想让 emit 发生挂起除了设置 DROP_XXX 之外还有一个方法就是调用tryEmit这是一个非 suspend 版本的 emitabstract suspend override fun emit(value: T) abstract fun tryEmit(value: T): BooleantryEmit 返回一个 boolean 值你可以这样判断返回值当使用 emit 会挂起时使用 tryEmit 会返回 false其余情况都是 true。这意味着 tryEmit 返回 false 的前提是 extraBufferCapacity 必须设为 SUSPEND且 Buffer 中空余位置为 0 。此时使用 tryEmit 的效果等同于 DROP_LATEST。SharedFlow Buffer前面介绍的 MutableSharedFlow 的三个参数其本质都是围绕 SharedFlow 的 Buffer 进行工作的。那么这个 Buffer 具体结构是怎样的呢上面这个图是 SharedFlow 源码中关于 Buffer 的注释这个图形象地告诉了我们 Buffer 是一个线性数据结构就是一个普通的数组ArrayAny?但是这个图不能直观反应 Buffer 运行机制。下面通过一个例子看一下 Buffer 在运行时的具体更新过程val sharedFlow MutableSharedFlowInt( replay 2, extraBufferCapacity 2, onBufferOverflow BufferOverflow.SUSPEND ) var emitValue 1 fun main() { runBlocking { launch { sharedFlow.onEach { delay(200) // simulate the consume of data }.collect() } repeat(12) { sharedFlow.emit(emitValue) emitValue delay(50) } } }上面的代码很简单SharedFlow 的 BufferSize 22 4Emitter 生产的速度大于 Subscriber 消费的速度所以过程中会出现 Buffer 的填充和更新下面依旧用图的方式展示 Buffer 的变化先看一下代码对应的时序图:有前面的介绍相信这个时序图很容易理解这里就不再赘述了下面重点图解一下 Buffer 的内存变化。SharedFlow 的 Buffer 本质上是一个基于 Array 实现的 queue通过指针移动从往队列增删元素避免了元素在实际数组中的移动。这里关键的指针有三个head队列的 head 指向 Buffer 的第一个有效数据这是时间上最早进入缓存的数据在数据被所有的 Subscriber 消费之前不会移除缓存。因此 head 也代表了最慢的 Subscriber 的处理进度replayBuffer 为 replay-cache 预留空间的其实位置当有新的 Subscriber 订阅发生时从此位置开始处理数据。end新数据进入缓存时的位置end 这也代表了最快的 Subscriber 的处理进度。如果 bufferSize 表示当前 Buffer 中存储数据的个数则我们可知三指针 index 符合如下关系replay head bufferSizeend head bufferSize了解了三指针的含义后我们再来看上图中的 Buffer 是如何工作的最后总结一下 Buffer 的特点基于数组实现当数组空间不够时进行 2n 的扩容元素进入数组后的位置保持不变通过移动指针决定数据的消费起点指针移动到数组尾部后会重新指向头部数组空间可循环使用最后如果想要成为架构师或想突破20~30K薪资范畴那就不要局限在编码业务要会选型、扩展提升编程思维。此外良好的职业规划也很重要学习的习惯很重要但是最重要的还是要能持之以恒任何不能坚持落实的计划都是空谈。如果你没有方向这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》帮大家将杂乱、零散、碎片化的知识进行体系化的整理让大家系统而高效地掌握Android开发的各个知识点。 相对于我们平时看的碎片化内容这份笔记的知识点更系统化更容易理解和记忆是严格按照知识体系编排的。一、架构师筑基必备技能1、深入理解Java泛型2、注解深入浅出3、并发编程4、数据传输与序列化5、Java虚拟机原理6、高效IO二、Android百大框架源码解析1.Retrofit 2.0源码解析2.Okhttp3源码解析3.ButterKnife源码解析4.MPAndroidChart 源码解析5.Glide源码解析6.Leakcanary 源码解析7.Universal-lmage-Loader源码解析8.EventBus 3.0源码解析9.zxing源码分析10.Picasso源码解析11.LottieAndroid使用详解及源码解析12.Fresco 源码分析——图片加载流程三、Android性能优化实战解析腾讯Bugly:对字符串匹配算法的一点理解爱奇艺安卓APP崩溃捕获方案——xCrash字节跳动深入理解Gradle框架之一Plugin, Extension, buildSrc百度APP技术Android H5首屏优化实践支付宝客户端架构解析Android 客户端启动速度优化之「垃圾回收」携程从智行 Android 项目看组件化架构实践网易新闻构建优化如何让你的构建速度“势如闪电”四、高级kotlin强化实战1、Kotlin入门教程2、Kotlin 实战避坑指南3、项目实战《Kotlin Jetpack 实战》从一个膜拜大神的 Demo 开始Kotlin 写 Gradle 脚本是一种什么体验Kotlin 编程的三重境界Kotlin 高阶函数Kotlin 泛型Kotlin 扩展Kotlin 委托协程“不为人知”的调试技巧图解协程suspend五、Android高级UI开源框架进阶解密1.SmartRefreshLayout的使用2.Android之PullToRefresh控件源码解析3.Android-PullToRefresh下拉刷新库基本用法4.LoadSir-高效易用的加载反馈页管理框架5.Android通用LoadingView加载框架详解6.MPAndroidChart实现LineChart折线图7.hellocharts-android使用指南8.SmartTable使用指南9.开源项目android-uitableview介绍10.ExcelPanel 使用指南11.Android开源项目SlidingMenu深切解析12.MaterialDrawer使用指南六、NDK模块开发1、NDK 模块开发2、JNI 模块3、Native 开发工具4、Linux 编程5、底层图片处理6、音视频开发7、机器学习七、Flutter技术进阶1、Flutter跨平台开发概述2、Windows中Flutter开发环境搭建3、编写你的第一个Flutter APP4、Flutter开发环境搭建和调试5、Dart语法篇之基础语法(一)6、Dart语法篇之集合的使用与源码解析(二)7、Dart语法篇之集合操作符函数与源码分析(三)八、微信小程序开发1、小程序概述及入门2、小程序UI开发3、API操作4、购物商场项目实战……全套视频资料一、面试合集二、源码解析合集三、开源框架合集欢迎大家一键三连支持若需要文中资料直接点击文末CSDN官方认证微信卡片免费领取↓↓↓