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...

相关推荐
一笑的小酒馆4 小时前
Android15适配16kb
android
CYRUS_STUDIO5 小时前
FART 脱壳不再全量!用一份配置文件精准控制节奏与范围
android·c++·逆向
mooyuan天天5 小时前
DVWA靶场通关笔记-SQL Injection Blind(SQL盲注 Impossible级别)
android·笔记·sql
CYRUS_STUDIO5 小时前
FART 自动化脱壳框架优化实战:Bug 修复与代码改进记录
android·操作系统·逆向
王喵喵喵5 小时前
每天一个安卓测试开发小知识之 (四)---常用的adb shell命令第二期 pm命令
android·测试
g_i_a_o_giao5 小时前
Android8 从系统启动到用户见到第一个Activity的流程源码分析(三)
android·linux·笔记·学习·安卓framework开发·安卓源码分析
BoomHe5 小时前
Android Studio 内联提示设置
android
paynnne6 小时前
TreeSet原理
android
一条上岸小咸鱼6 小时前
Flutter 类和对象(二):继承
android·kotlin