查看原文
其他

打造一个 Kotlin Flow 版的 EventBus

AndroidPub 2022-07-13

作者:搬砖小子出现了
https://juejin.cn/post/6985093305470025764

背景 跨页面通信是一个比较常见的场景,通常我们会选择使用EventBus,但EventBus无法感知声明周期,收到消息就会回调,所以有了LiveData之后很快就有了LiveEventBus。不过它也有缺点,比如不能切换线程。

现在SharedFlow稳定了,那是不是也能搞一波?

背景

跨页面通信是一个比较常见的场景,通常我们会选择使用 EventBus,但 EventBus 无法感知声明周期,收到消息就会回调,所以有了 LiveData 之后很快就有了 LiveEventBus。不过它也有缺点,比如不能切换线程。现在 SharedFlow 稳定了,那是不是也能搞一波?

于是有了这次向大家介绍的 FlowEventBus

常见消息总线对比

消息总线延迟发送有序接收消息粘性事件(Sticky)生命周期可感知
EventBus
RxBus
LiveEventBus
FlowEventBus

设计构思

目前 AndroidX 鼓励开发者使用 StateFlow 替代 LiveData。受此启发,以 SharedFlow 为基础,实现了 FlowEventBus。其具备以下优点:

  • 依托协程轻松切换线程
  • 可以通过 replay 实现粘性效果
  • 可以被多个观察者订阅
  • 无观察者自动清除事件不会造成积压

结合 Lifecycle 感知生命周期,做到响应时机可控


依赖库版本

  • kotlinx-coroutines : 1.4.x
  • lifecycle-runtime-ktx : 2.3.x

API 设计

事件订阅

observeEvent<String>(eventName="SimpleEvent") { value ->
   ...
}

事件发送

postEvent(eventName="SimpleEvent",eventValue="Let's do it")

自定义事件订阅

observeEvent<CustomEvent> { event ->
  ...
}

自定义发送发送

postEvent(CustomEvent(value = "Hello Word"))

延迟发送

postDelayEvent(CustomEvent(name = "Hello Word"),1000)

发送粘性事件

postStickyEvent(eventName = STICKY,value = "☝ 粘性事件️")

指定最小感知生命周期(默认 Lifecycle.State.Started)

observeEvent<String>("SimpleEvent",Lifecycle.State.DESTROYED) { value ->
   ...
}

切换接收线程

observeEvent<String>("SimpleEvent",Dispatchers.IO) { value ->
    ...
}

实现原理

依托于 Kotlin 协程的 SharedFlowLifecycle, 实现起来非常简单:

粘性事件

MutableSharedFlow<Any>(
    replay = if (isSticky) 1 else 0,    //重播给新订阅者的消息数量
    extraBufferCapacity = Int.MAX_VALUE //避免挂起导致发送阻塞
)

生命周期感知

fun <T> LifecycleOwner.launchWhenStateAtLeast(
    minState: Lifecycle.State,
    block: suspend CoroutineScope.() -> T
)
 {
    lifecycleScope.launch {
        lifecycle.whenStateAtLeast(minState, block)
    }
}

切换线程

whenStateAtLeast 由于执行的block默认是在主线程,因此需要手动切换线程:
lifecycleOwner.launchWhenStateAtLeast(minState) {
    flow.collect { value ->
        lifecycleOwner.lifecycleScope.launch(dispatcher) {
                onReceived.invoke(value as T)
        }
    }
}

延迟事件

viewModelScope.launch {
    delay(time)
    flow.emit(value)
}

有序分发

Flow 本质类似阻塞队列, 可以保证有序。

全局单例

使用全局 ViewModel ,因为有 ViewModelScope, 避免使用 GlobalScope:

object ApplicationScopeViewModelProvider : ViewModelStoreOwner {

    private val eventViewModelStore: ViewModelStore = ViewModelStore()

    override fun getViewModelStore(): ViewModelStore {
        return eventViewModelStore
    }

    private val mApplicationProvider: ViewModelProvider by lazy {
        ViewModelProvider(
            ApplicationScopeViewModelProvider,
            ViewModelProvider.AndroidViewModelFactory.getInstance(EventBusInitializer.application)
        )
    }

    fun <T : ViewModel> getApplicationScopeViewModel(modelClass: Class<T>): T {
        return mApplicationProvider[modelClass]
    }
}

总结

项目虽小,可以借机学习一下Flow的功能和实现原理,收获多多~


Coroutines Flow:
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/index.html

仓库地址
https://github.com/Palardin3/flow-event-bus



- FIN -


推荐阅读
kotlin协程:并发 & 线程安全
AAB 什么鬼?竟敢打压鸿蒙?
【程序员必读】编程的智慧 by 王垠
建议收藏!Kotlin 线程同步的 N 种方法


加好友拉你进群,技术干货聊不停


↓关注公众号↓↓添加微信交流↓


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存