热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

什么?你还在用EventBus吗?快来试试用Flowapi搞一个!

1.前言谷歌推出flowapi已经很久了,俗称为数据流。我们老规矩看下定义,数据流以协程为基础构建,可提供多个值。从概念上来讲,数据流是可通过异步方式进行计算处理的一组数据序列。例

1.前言
谷歌推出flow api已经很久了,俗称为数据流。
我们老规矩看下定义,数据流以协程为基础构建,可提供多个值。从概念上来讲,数据流是可通过异步方式进行计算处理的一组数据序列。例如,Flow 是发出整数值的数据流。
数据流与生成一组序列值的

Iterator

非常相似,但它使用挂起函数通过异步方式生成和使用值。这就是说,例如,数据流可安全地发出网络请求以生成下一个值,而不会阻塞主线程。这么一大堆,其实就是说,flow就像一个流一样,可以在其中生成数值,并且可在协程中使用,哎呦,官方是真的疼爱Coroutine。
基于flow api这种特性,我们可以在很多数据通讯场景中得到使用,同时由于flow 在协程场景下运用,可以用来封装很多有趣的东西,本文就带来了一个利用该api封装的总线型库。



  1. 预演

前面讲了一大堆概念,那么我们该看一下具体代码表现了,首先是Flow

public interface Flow<out T> {
@InternalCoroutinesApi
public suspend fun collect(collector: FlowCollector<T>)
}

FlowCollector接口里面定义了flow api的发送规范,注意其也是一个suspend方法,它就属于消息的发送方。
现在我们理清楚了,原来就是定义了一个接收方跟发送方的规范,这里也能看出来为什么普通flow是个冷流(注意是普通,flow可以实现热流,比如StateFlow),你看,flow接口中的collect,其实就是接受一个FlowCollector,调用其里面的emit才会发送数据。还有一个小细节,就是flow里面的泛型是out修饰,然而FlowCollector是in修饰,这里可以留给读者们思考为什么这么设计。

3.跟现有的数据类型区别
StateFlow、Flow 和 LiveData
因为我们接下来会运用到SharedFlow,所以先过一下区别
StateFlow 和 LiveData 具有相似之处。两者都是可观察的数据容器类,并且在应用架构中使用时,两者都遵循相似模式。
但请注意,StateFlow 和 LiveData 的行为确实有所不同:



  • StateFlow 需要将初始状态传递给构造函数,而 LiveData 不需要。

  • 当 View 进入 STOPPED 状态时,LiveData.observe() 会自动取消注册使用方,而从 StateFlow 或任何其他数据流收集数据的操作并不会自动停止。

【来自Android开发者官网】
那么StateFlow和SharedFlow的区别呢

public interface StateFlow<out T> : SharedFlow<T> {

public val value: T
}

看到上面代码就水落石出了,SharedFlow是StateFlow更高一级的抽象,而SharedFlow是

public interface SharedFlow<out T> : Flow<T> {
/**
* A snapshot of the replay cache.
*/
public val replayCache: List
}

可以看到,里面存在着一个list对象,这就说明了数据流的来源了,其实最本质的数据结构就是一个list。看到这里,是不是很想我们总线型的框架内部核心了呢,那么我们开始封装我们的总线数据流吧
4.开始搞事情
我们需要实现的东西是啥:
1.总线型数据流,内部可以用flow api实现,这里可以采用SharedFlow。
2.相较于EventBus需要注册解除注册,相信各位大佬们肯定很烦了,那么我们就需要自动注册解除注册这种功能啦。
3.可以发送粘性事件与非粘性事件
4.切换线程订阅..
5.足够精简
6.实现发送和接收核心功能

我们基于上面的问题,开始一步步实现:
Q1:根据上面源码分析,SharedFlow里面就维护着一个list,满足了我们消息存放的数据结构,所以没问题

Q2:自动注册解除注册这个,嘿嘿,大家看都不用看,就会想到LifecycleEventObserver这个接口,毕竟监听生命周期嘛,老套路了,那么我们哪里实现这个接口呢?flow?事件里面?nonono,我们一直忽略了一个重要的点,就是协程,上面说了一堆东西,其实flow运行在协程里面对不对,我们可以在协程里面做文章呀!也就是说,我们控制了协程生命周期,不就是控制了flow的生命周期了嘛!

class LifeCycleJob(private val job: Job) : Job by job, LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
this.cancel()
}
}
override fun cancel(cause: CancellationException?) {
if (!job.isCancelled) {
job.cancel()
}
}
}

Q3:如何区分粘性事件和非粘性事件,这个也很好处理,粘性数据的话,就让接收方接受数据的时候,把list里面的数据再发送一遍不就可以了,非粘性订阅就不发送由于我们现在想要单个事件的订阅所以可以利用 MutableSharedFlow实现

@Suppress("FunctionName", "UNCHECKED_CAST")
public fun MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow {
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)
}

看到了MutableSharedFlow实现实现了MutableSharedFlow接口,而MutableSharedFlow接口又实现了SharedFlow,FlowCollector,所以可以充当发送者与收集者啦!

interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T>

看到MutableSharedFlow参数,所以我们可以直接利用其特性: 对于事件集合,我们可以这样定义消息数据集合:

非粘性
var events = ConcurrentHashMap<Any, MutableSharedFlow<Any>>()
private set
粘性
var stickyEvents = ConcurrentHashMap<Any, MutableSharedFlow<Any>>()
private set

Q4:切换线程,这个不就是协程最擅长的嘛,利用Dispatch切换指定响应线程即可
Q5:足够精简的话,这里采用扩展函数,就可以非常方便原有功能的拓展。
Q6:前面说了MutableSharedFlow实现了flow发送和接收的接口,所以我们可以利用这个特性

inline fun post(event: T, isStick: Boolean) {
val cls = T::class.java
if (!isStick) {
stickyEvents.getOrElse(cls) {
MutableSharedFlow(0, 1, BufferOverflow.DROP_OLDEST)
}.tryEmit(event as Any)
} else {
stickyEvents.getOrElse(cls) {
MutableSharedFlow(1, 1, BufferOverflow.DROP_OLDEST)
}.tryEmit(event as Any)
}
}

那么接收呢,接收就有点麻烦了,因为我们发送都是用的post方法,于是如何区分粘性消息和非粘性消息呢?这里我们采用都监听的方式,那么是在同一个协程域里面监听还是不同的监听呢?这里又涉及到了一个小问题,collect函数会挂起当前协程,所以如果采用同一个协程域内监听的话,显然是不行的,因为同一个协程域内(不考虑存在子协程域)的情况下,其实运行是串行的,所以我们需要开两个协程域,分别在里面调用collect函数,监听粘性事件与非粘性事件

在这里插入图片描述

inline fun <reified T> onEvent(
event: Class<T>,
crossinline dos: (T) -> Unit,
owner: LifecycleOwner,
env: SubscribeEnv
) {
if (!events.containsKey(event)) {
events[event] = MutableSharedFlow(0, 1, BufferOverflow.DROP_OLDEST)
}
if (!stickyEvents.containsKey(event)) {
stickyEvents[event] = MutableSharedFlow(1, 1, BufferOverflow.DROP_OLDEST)
}
val coroutineScope: CoroutineScope = when (env) {
SubscribeEnv.IO -> CoroutineScope(Dispatchers.IO)
SubscribeEnv.DEFAULT -> CoroutineScope(Dispatchers.Default)
else -> CoroutineScope(Dispatchers.Main)
}
coroutineScope.launch {
events[event]?.collect {
if (it is T) {
dos.invoke(it)
}
}
}.setLifeCycle(owner.lifecycle)
coroutineScope.launch {
stickyEvents[event]?.collect {

if (it is T) {
dos.invoke(it)
}
}
}.setLifeCycle(owner.lifecycle)
}

由于文章篇幅有限,文档资料内容较多,需要《2022最新Android面试真题+解析》、数据结构与算法面试题、Java 面试题、Android四大组件、Android 面试题、UI控件篇、网络通信篇、架构设计篇、性能优化篇、源码流程篇、 Kotlin方面、第三方框架、大厂面经,可以【点击这里免费获取】,希望能够共同进步,共同学习,共勉!



推荐阅读
  • Oracle10g备份导入的方法及注意事项
    本文介绍了使用Oracle10g进行备份导入的方法及相关注意事项,同时还介绍了2019年独角兽企业重金招聘Python工程师的标准。内容包括导出exp命令、删用户、创建数据库、授权等操作,以及导入imp命令的使用。详细介绍了导入时的参数设置,如full、ignore、buffer、commit、feedback等。转载来源于https://my.oschina.net/u/1767754/blog/377593。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 本文介绍了Java集合库的使用方法,包括如何方便地重复使用集合以及下溯造型的应用。通过使用集合库,可以方便地取用各种集合,并将其插入到自己的程序中。为了使集合能够重复使用,Java提供了一种通用类型,即Object类型。通过添加指向集合的对象句柄,可以实现对集合的重复使用。然而,由于集合只能容纳Object类型,当向集合中添加对象句柄时,会丢失其身份或标识信息。为了恢复其本来面貌,可以使用下溯造型。本文还介绍了Java 1.2集合库的特点和优势。 ... [详细]
  • 本文介绍了pack布局管理器在Perl/Tk中的使用方法及注意事项。通过调用pack()方法,可以控制部件在显示窗口中的位置和大小。同时,本文还提到了在使用pack布局管理器时,应注意将部件分组以便在水平和垂直方向上进行堆放。此外,还介绍了使用Frame部件或Toplevel部件来组织部件在窗口内的方法。最后,本文强调了在使用pack布局管理器时,应避免在中间切换到grid布局管理器,以免造成混乱。 ... [详细]
  • 在IDEA中运行CAS服务器的配置方法
    本文介绍了在IDEA中运行CAS服务器的配置方法,包括下载CAS模板Overlay Template、解压并添加项目、配置tomcat、运行CAS服务器等步骤。通过本文的指导,读者可以轻松在IDEA中进行CAS服务器的运行和配置。 ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
author-avatar
唐古拉风情2502931431
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有