Android使用Flow+协程封装一个FlowBus

Android使用Flow+协程封装一个FlowBus

前言:

做过Android的同学应该都使用过EvenutBus、Rxbus、LiveDataBus等,这些第三方不仅要导入依赖包,而且还要注册和取消注册,使用起来非常麻烦,稍不注意就导致内存泄漏,自从接触了Flow、SharedFlow之后感觉使用起来方便多了,于是产生了一个封装通用事件工具类的想法,直接上代码.

1.FlowBus工具类:

kotlin 复制代码
/**
 * @auth: njb
 * @date: 2024/7/18 10:17
 * @desc: 基于Flow封装的FlowBus
 */
object FlowBus {
    private const val TAG = "FlowBus"
    private val busMap = mutableMapOf<String, FlowEventBus<*>>()
    private val busStickMap = mutableMapOf<String, FlowStickEventBus<*>>()
​
    @Synchronized
    fun <T> with(key: String): FlowEventBus<T> {
        var flowEventBus = busMap[key]
        if (flowEventBus == null) {
            flowEventBus = FlowEventBus<T>(key)
            busMap[key] = flowEventBus
        }
        return flowEventBus as FlowEventBus<T>
    }
​
    @Synchronized
    fun <T> withStick(key: String): FlowStickEventBus<T> {
        var stickEventBus = busStickMap[key]
        if (stickEventBus == null) {
            stickEventBus = FlowStickEventBus<T>(key)
            busStickMap[key] = stickEventBus
        }
        return stickEventBus as FlowStickEventBus<T>
    }
​
    open class FlowEventBus<T>(private val key: String) : DefaultLifecycleObserver {
        //私有对象用于发送消息
        private val _events: MutableSharedFlow<T> by lazy {
            obtainEvent()
        }
​
        //暴露的公有对象用于接收消息
        private val events = _events.asSharedFlow()
​
        open fun obtainEvent(): MutableSharedFlow<T> =
            MutableSharedFlow(0, 1, BufferOverflow.DROP_OLDEST)
​
        //在主线程中接收数据
        fun register(lifecycleOwner: LifecycleOwner,action: (t: T) -> Unit){
            lifecycleOwner.lifecycleScope.launch {
                events.collect {
                    try {
                        action(it)
                    }catch (e:Exception){
                        e.printStackTrace()
                        Log.e(TAG, "FlowBus - Error:$e")
                    }
                }
            }
        }
​
        //在协程中接收数据
        fun register(scope: CoroutineScope,action: (t: T) -> Unit){
            scope.launch {
                events.collect{
                    try {
                       action(it)
                    }catch (e:Exception){
                        e.printStackTrace()
                        Log.e(TAG, "FlowBus - Error:$e")
                    }
                }
            }
        }
​
        //在协程中发送数据
        suspend fun post(event: T){
            _events.emit(event)
        }
​
        //在主线程中发送数据
        fun post(scope: CoroutineScope,event: T){
            scope.launch {
                _events.emit(event)
            }
        }
​
        override fun onDestroy(owner: LifecycleOwner) {
            super.onDestroy(owner)
            Log.w(TAG, "FlowBus ==== 自动onDestroy")
            val subscriptCount = _events.subscriptionCount.value
            if (subscriptCount <= 0)
                busMap.remove(key)
        }
​
        // 手动调用的销毁方法,用于Service、广播等
        fun destroy() {
            Log.w(TAG, "FlowBus ==== 手动销毁")
            val subscriptionCount = _events.subscriptionCount.value
            if (subscriptionCount <= 0) {
                busMap.remove(key)
            }
        }
​
    }
​
    class FlowStickEventBus<T>(key: String) : FlowEventBus<T>(key) {
        override fun obtainEvent(): MutableSharedFlow<T> =
            MutableSharedFlow(1, 1, BufferOverflow.DROP_OLDEST)
    }
​
​
}

2.在Activity中的使用:

2.1传递参数给主界面Activity:

kotlin 复制代码
/**
 * @auth: njb
 * @date: 2024/9/10 23:49
 * @desc: 描述
 */
class TestActivity :AppCompatActivity(){
    private val textView:TextView by lazy { findViewById(R.id.tv_test) }
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        initFlowBus()
    }
​
    private fun initFlowBus() {
        val messageEvent = MessageEvent()
        messageEvent.message = "stop"
        messageEvent.state = false
        textView.setOnClickListener {
            lifecycleScope.launch {
                FlowBus.with<MessageEvent>("test").post(this, messageEvent)
                finish()
            }
        }
    }
}

2.2 MainActivity接收:

less 复制代码
/**
 * 初始化
 */
private fun initView() {
    binding.rvWallpaper.apply {
        layoutManager = GridLayoutManager(this@MainActivity, 2)
        adapter = wallPaperAdapter
    }
    binding.btnGetWallpaper.setOnClickListener {
        lifecycleScope.launch {
            mainViewModel.mainIntentChannel.send(MainIntent.GetWallpaper)
        }
        val intent = Intent(this@MainActivity,TestActivity::class.java)
        startActivity(intent)
    }
    FlowBus.with<MessageEvent>("test").register(this@MainActivity) {
        LogUtils.d(TAG,it.toString())
        if(it.message == "stop"){
            LogUtils.d(TAG,"===接收到的消息为==="+it.message)
        }
    }
    FlowBus.with<MessageEvent>("mineFragment").register(this@MainActivity) {
        LogUtils.d(TAG,it.toString())
        if(it.message == "onMine"){
            LogUtils.d(TAG,"===接收到的消息为1111==="+it.message)
        }
    }
}

3.在Fragment中的使用:

3.1 发送数据

kotlin 复制代码
package com.cloud.flowbusdemo.fragment
​
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.cloud.flowbusdemo.databinding.FragmentMineBinding
import com.cloud.flowbusdemo.flow.FlowBus
import com.cloud.flowbusdemo.model.MessageEvent
import kotlinx.coroutines.launch
​
private const val ARG_PARAM_NAME = "name"
private const val ARG_PARAM_AGE = "age"
/**
 * @auth: njb
 * @date: 2024/9/17 19:43
 * @desc: 描述
 */
class MineFragment :Fragment(){
    private lateinit var binding: FragmentMineBinding
    private val TAG = "MineFragment"
    private var name: String? = null
    private var age: Int? = null
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            name = it.getString(ARG_PARAM_NAME)
            age = it.getInt(ARG_PARAM_AGE)
        }
        Log.i(TAG, "MainFragment 传递到 MineFragment 的参数为 name = $name , age = $age")
        Log.d(TAG, "姓名:" + name + "年龄:" + age)
    }
​
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentMineBinding.inflate(layoutInflater)
        initView()
        return binding.root
    }
​
​
    private fun initView() {
        val messageEvent = MessageEvent()
        messageEvent.message = "onMine"
        messageEvent.state = false
        binding.let {
            it.tvTitle.text = name
            it.tvAge.text  = age.toString()
            it.tvTitle.setOnClickListener {
                lifecycleScope.launch {
                    FlowBus.with<MessageEvent>("mineFragment").post(this, messageEvent)
                }
            }
        }
    }
}
less 复制代码
![image.png](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/6b2ec493be634d36b5fc50c554650e7c~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgQW5kcm9pZOWwj-a4o-a4ow==:q75.awebp?rk3s=f64ab15b&x-expires=1757484606&x-signature=HpL%2FQu%2Fsxrc%2BIX7b3zgIZWhgtIE%3D)

## 3.2 接收数据:

private fun initView() {
    binding.rvWallpaper.apply {
        layoutManager = GridLayoutManager(this@MainActivity, 2)
        adapter = wallPaperAdapter
    }
    binding.btnGetWallpaper.setOnClickListener {
        lifecycleScope.launch {
            mainViewModel.mainIntentChannel.send(MainIntent.GetWallpaper)
        }
        val intent = Intent(this@MainActivity,TestActivity::class.java)
        startActivity(intent)
    }
    FlowBus.with<MessageEvent>("test").register(this@MainActivity) {
        LogUtils.d(TAG,it.toString())
        if(it.message == "stop"){
            LogUtils.d(TAG,"===接收到的消息为==="+it.message)
        }
    }
    FlowBus.with<MessageEvent>("mineFragment").register(this@MainActivity) {
        LogUtils.d(TAG,it.toString())
        if(it.message == "onMine"){
            LogUtils.d(TAG,"===接收到的消息为1111==="+it.message)
        }
    }
}

4.在Service中的使用:

4.1发送数据:

less 复制代码
private fun initService() {
    val intent = Intent(this@MainActivity, FlowBusTestService::class.java)
    intent.putExtra("sockUrl","")
    startService(intent)
}


![image.png](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/211f50ed3bde4f7eb00f598ad7c70e8f~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgQW5kcm9pZOWwj-a4o-a4ow==:q75.awebp?rk3s=f64ab15b&x-expires=1757484606&x-signature=Y4pX1gPmM3vQMApMrR%2Be%2BsBd8Eg%3D)

4.2接收数据:

kotlin 复制代码
/**
 * @auth: njb
 * @date: 2024/9/22 23:32
 * @desc: 描述
 */
class FlowBusTestService:Service() {
    private var sock5Url:String ?= null
    private val TAG = "FlowBusTestService"
​
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
​
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        intent?.let {
            this.sock5Url = intent.getStringExtra("sockUrl")
            LogUtils.d(TAG,"====收到的ip为==="+this.sock5Url)
        }
        return if (intent?.action == Constants.ACTION_DISCONNECT) {
            disconnect()
            START_NOT_STICKY
        } else {
            connect()
            START_STICKY
        }
    }
​
    private fun connect() {
​
    }
​
    private fun disconnect() {
​
    }
}

5.在Websock中的使用:

5.1发送数据:

kotlin 复制代码
private fun connectWebSocket() {
    LogUtils.e(TAG, "===connectUrl===$currentWebSocketUrl")
    try {
        if (mWebSocketManager == null) {
            return
        }
        mWebSocketManager?.addListener(object : SocketListener {
            override fun onConnected() {
                LogUtils.e(TAG, "===连接成功====")
                val messageEvent = MessageEvent()
                messageEvent.message = "socket连接成功"
                FloatWindowManager.log("socket连接成功")
                CoroutineScope(Dispatchers.Main).launch{
                    FlowBus.with<MessageEvent>("onConnected").post(this,messageEvent)
                }
                //val msg1 = "{"clientUA":"uwWFPvfkyY","errorMsg":"该手机号无法办理业务","event":"after_order_check","eventOrderSeq":"nXgBlfCuFruOhwOIvpkK","eventTime":1721270790010,"id":"c8a8fabdd7fb4f0c987b6c8634276cbc","mock":true,"orderSn":"CHJdxETRqORZxkckZMTa","phone":"neO714HKa+So1pmhQ0ZEfg==","switchIp":true,"pageViewUrl":"https://hippoglo.com/yidong/test/fu21/index.html"}"
            }
​
            override fun onConnectFailed(throwable: Throwable) {
                LogUtils.e(TAG, "===连接失败====")
                val messageEvent = MessageEvent()
                messageEvent.message = "socket连接失败:$currentWebSocketUrl"
                FloatWindowManager.log("socket连接失败")
            }
​
            override fun onDisconnect() {
                LogUtils.e(TAG, "===断开连接====")
                val messageEvent = MessageEvent()
                messageEvent.message = "socket断开连接"
                FloatWindowManager.log("socket断开连接")
            }
​
            override fun onSendDataError(errorResponse: ErrorResponse) {
                LogUtils.e(TAG + "===发送数据失败====" + errorResponse.description)
                val messageEvent = MessageEvent()
                messageEvent.message = "发送数据失败--->" + errorResponse.description
                FloatWindowManager.log("发送数据失败")
            }
​
            override fun <T> onMessage(msg: String, t: T) {
                LogUtils.e(TAG,"===接收到消息 String===$msg")
                val messageEvent = MessageEvent()
                messageEvent.message = msg
                FloatWindowManager.log("===接收到消息===$msg")
                taskManager?.onHandleMsg(msg)
​
            }
​
            override fun <T> onMessage(bytes: ByteBuffer, t: T) {
                LogUtils.e(TAG, "===接收到消息byteBuffer===="+GsonUtils.toJson(bytes))
                val rBuffer = ByteBuffer.allocate(1024)
                val charset = Charset.forName("UTF-8")
                try {
                    val receiveText =
                        charset.newDecoder().decode(rBuffer.asReadOnlyBuffer()).toString()
                    LogUtils.e(TAG, "===接收到消息byteBuffer====$receiveText")
                    val messageEvent = MessageEvent()
                    messageEvent.message = receiveText
                   // FloatWindowManager.log("===收到消息 byte===$receiveText")
                } catch (e: CharacterCodingException) {
                    throw RuntimeException(e)
                }
            }
​
            override fun onPing(pingData: Framedata) {
                LogUtils.e(TAG, "===心跳onPing===$pingData")
            }
​
            override fun onPong(framedata: Framedata) {
                LogUtils.e(TAG, "===心跳onPong===$framedata")
                val messageEvent = MessageEvent()
                messageEvent.message = format.format(Date()) + "  | 心跳onPong->"
                FloatWindowManager.log("===心跳onPong===${format.format(Date())}${"->"}$currentWebSocketUrl")
            }
        })
        mWebSocketManager?.start()
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

5.2接收数据:

less 复制代码
private fun initFlowBus() {
    FlowBus.with<MessageEvent>("onConnected").register(this@MainActivity) {
        LogUtils.d(TAG, "收到消息为:$it")
    }
    FlowBus.with<MessageEvent>("onStartVpn").register(this@MainActivity) {
        LogUtils.d(TAG, "收到vpn消息为:$it")
        CoroutineScope(Dispatchers.Main).launch {
            if (it.message == "start" && it.state && Constants.SWITCH_IP) {
                this@MainActivity.sockUrl = it.sockUrl
                LogUtils.d(TAG, "收到代理地址为:${it.sockUrl}")
                AppUtils.prepareVpn(this@MainActivity,it.sockUrl)
               // prepareVpn()
            }
        }
    }
    FlowBus.with<MessageEvent>("onStopVpn").register(this@MainActivity) {
        LogUtils.d(TAG, "收到vpn消息为:$it")
        if (it.message == "stop" && !it.state) {
            AppUtils.stopVpn(this@MainActivity)
        }
    }
}

6.实现的效果如下:

7.FlowBus、EventBus 与 RxBus对比

对比维度 FlowBus EventBus RxBus
底层依赖 基于 Android 官方 Kotlin Flow(协程生态核心组件) 无第三方依赖,基于自定义观察者模式实现 基于 RxJava(响应式编程库,需引入 RxJava 依赖)
支持语言 仅支持 Kotlin(依赖协程与 Flow 的 Kotlin 语法特性) 支持 Java 和 Kotlin(兼容 Java 语法,无语言绑定) 支持 Java 和 Kotlin(RxJava 本身兼容两种语言)
线程切换 天然支持协程线程切换(通过 flowOn/launchIn 灵活指定发送 / 接收线程,无需手动处理线程安全) 需通过 ThreadMode 显式指定线程(如 MAIN/BACKGROUND,底层通过 Handler / 线程池实现) 依赖 RxJava 操作符(如 observeOn/subscribeOn)切换线程,支持更细粒度的线程控制
生命周期感知 支持(需配合 LifecycleOwner,通过 repeatOnLifecycle 自动绑定页面生命周期,避免内存泄漏) 不原生支持(需手动注册 / 解注册,若忘记解注册易导致 Activity/Fragment 内存泄漏,需通过 EventBus 3.x 注解或封装类优化) 不原生支持(需手动管理订阅 / 取消订阅,或通过 RxLifecycle 等扩展库绑定生命周期,否则易引发内存泄漏)
事件类型 基于泛型严格区分事件类型(发送 / 接收需匹配泛型,编译期可检查类型错误,减少运行时异常) 基于事件类实例区分(通过 post(Object event) 发送,接收时需强转,存在运行时类型转换风险) 基于泛型区分事件类型(类似 FlowBus,编译期可检查类型,但需依赖 RxJava 的 Observable 泛型定义)
背压处理 原生支持背压(Flow 本身是背压感知流,可通过 buffer/conflate/collectLatest 处理高频率事件堆积) 不支持背压(事件发送无缓冲控制,高频率发送时易导致接收端处理不及时,引发 ANR 或数据丢失) 支持背压(依赖 RxJava 的背压策略,如 BACKPRESSURE_BUFFER/BACKPRESSURE_DROP,但配置较复杂)
异常处理 基于协程异常处理机制(通过 catch 操作符捕获异常,或在 collect 块中 try-catch,异常不崩溃主线程) 异常需手动捕获(若接收事件的方法抛出未捕获异常,会直接崩溃应用,需在订阅者方法内显式处理) 依赖 RxJava 异常处理(通过 onError 回调捕获异常,若未处理 onError 会触发 OnErrorNotImplementedException 崩溃)
使用复杂度 中(需理解协程与 Flow 基础概念,上手成本依赖 Kotlin 协程熟练度) 低(API 简洁,通过 @Subscribe 注解 +register/unregister 即可使用,学习成本低) 高(需掌握 RxJava 操作符、线程模型、背压策略等,API 较复杂,上手门槛高)
性能 高(Flow 是轻量级流,基于协程挂起机制,无额外线程切换开销,内存占用低) 中(自定义观察者模式实现,线程切换依赖 Handler / 线程池,存在少量开销,整体性能稳定) 中(RxJava 底层封装较复杂,操作符链可能引入额外对象创建开销,高频率场景下性能略逊于 FlowBus)
优缺点总结 优点 : 1. 官方生态,兼容性强(随 Kotlin 协程迭代更新) 2. 生命周期感知,自动防内存泄漏 3. 支持背压,高频率事件处理更安全 4. 编译期类型检查,减少运行时错误 缺点: 1. 仅支持 Kotlin,无法兼容 Java 项目 2. 依赖协程知识,上手成本高于 EventBus 优点 : 1. 无依赖,接入简单,学习成本低 2. 兼容性好,支持 Java/Kotlin 项目 3. 社区成熟,文档丰富,问题易排查 缺点: 1. 不原生支持生命周期,需手动管理注册 / 解注册 2. 无背压,高频率事件易引发性能问题 3. 事件接收需强转,存在运行时类型风险 优点 : 1. 支持丰富的操作符(如过滤、变换、合并),复杂事件流处理能力强 2. 线程控制灵活,支持多线程场景 3. 支持背压,可应对高频率事件 缺点: 1. 依赖 RxJava,增加 APK 体积(约 1-2MB) 2. 上手门槛高,需掌握响应式编程思想 3. 不原生支持生命周期,需额外集成 RxLifecycle
适用场景 1. Kotlin 语言的 Android 项目 2. 需生命周期感知、避免内存泄漏的场景(如 Activity/Fragment 通信) 3. 高频率事件场景(如实时数据刷新) 1. Java/Kotlin 混合项目或纯 Java 项目 2. 简单事件通信场景(如页面跳转、通知更新) 3. 对学习成本敏感、追求轻量接入的项目 1. 已引入 RxJava 生态的项目(避免重复依赖) 2. 复杂事件流处理场景(如多事件合并、过滤) 3. 需精细控制线程与背压的场景

8.总结:

  • 通过上面的实例和使用测试,可以发现使用非常简单方便
  • 不需要导入任何第三方依赖库,都是原生自带的
  • 在Activity、Fragment和ViewModel中不需要关注生命周期和解绑问题
  • 在Service或者广播等其他场景都可以使用,只要你想用,基本上都能
  • 比起EventBus、Rxbus使用都要简单方便,可扩展性更好
  • 后面如果有时间的话考虑加入跨进程通信方案
  • 如果小伙伴们有更好的方案或者扩展欢迎讨论,毕竟一个人精力有限

9.项目demo源码如下:

gitee.com/jackning_ad...

相关推荐
Kapaseker3 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴3 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭13 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab14 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe19 小时前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋1 天前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我2 天前
让我们实现一个更好看的内部阴影按钮
android·flutter