EventBus怎么用?一起学习下

152 阅读2分钟

EventBus使用

EventBus优点

  1. 简化组件之间的通信

    • 解耦发送者和接收者
    • 可以在Fragment、Activity和不同的线程之间使用
    • 避免了复杂的依赖
  2. 简化代码

  3. 快且小,60K

  4. 已经有上亿的下载量

  5. 具有很多高级特性

使用方法

  1. EventBus.getDefault().register(this)注册当前对象
  2. 使用@Subscribe注解要接收Event的方法
  3. 定义事件(Event)类,本文里定义一个EventBusMessage类
  4. EventBus.getDefault().post(EventBusMessage())发送事件
class MainActivity : AppCompatActivity() {
    val TAG = "MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val btn = findViewById<TextView>(R.id.helloEventBus)
        btn.setOnClickListener {
            for (i in 0 until 3){
                Log.d(TAG, "post Thread: ${Thread.currentThread()}")
                EventBus.getDefault().post(EventBusMessage())
            }
        }
    }
​
    // 注册
    override fun onStart() {
        EventBus.getDefault().register(this)
        super.onStart()
    }
​// Event的类型和订阅的类都可以自定义,关键是Subscribe注解
    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onMessageEvent(event: EventBusMessage?) {
        Log.d(TAG, "onMessageEvent: ${event!!.name} Receiver Thread: ${Thread.currentThread()}")
    }
​
    override fun onStop() {
        EventBus.getDefault().unregister(this)
        super.onStop()
    }
}

定义一个事件类:

class EventBusMessage(val i: Int) {
    public val name = "cool!+${i}"
    companion object {
        private const val TAG = "EventBusMessage"
    }
}

解释一下

注意事项

  1. 注册的EventBus对象和post的是一个对象才能成功,EventBus.getDefault()拿到的是默认的单例,不同model引用eventbus包名一致,所以EventBus.getDefault()拿到的是同一对象
  2. 订阅的方法必须是public的
  3. 不用的时候要调用EventBus.getDefault().unregister(this)

点击btn,onMessageEvent就会打印方法

虽然这个例子并没有实际意义,但是可以很好的解释用法

@Subscribe 注解介绍

完整版如下:

@Subscribe(threadMode = ThreadMode.MAIN,sticky = true,priority = 1)

threadMode

先看下简单的一段源码,应该更容易理解

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case MAIN_ORDERED:
            if (mainThreadPoster != null) {
                mainThreadPoster.enqueue(subscription, event);
            } else {
                // temporary: technically not correct as poster not decoupled from subscriber
                invokeSubscriber(subscription, event);
            }
            break;
        case BACKGROUND:
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}

POSTING

这是EventBus的默认模式,表示post事件是什么线程,onReceiveMsg接收事件方法就在同样的线程环境中执行代码。更改代码如下

findViewById.setOnClickListener {
    Thread{
        for (i in 0 until 3){
            Log.d(TAG, "post Thread: ${Thread.currentThread()}")
            EventBus.getDefault().post(EventBusMessage())
        }
    }.start()
}
@Subscribe(threadMode = ThreadMode.POSTING)
fun onMessageEvent(event: EventBusMessage?) {
    Log.d(TAG, "Receiver Thread: ${Thread.currentThread()} onMessageEvent: ${event!!.name} ")
}

从源码里可以看出,当ThreadMode是Posting,则直接执行invokeSubscriber(subscription, event),

void invokeSubscriber(Subscription subscription, Object event) {
    try {
    // 通过反射调用,所以是串行
        subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    } catch (InvocationTargetException e) {
        handleSubscriberException(subscription, event, e.getCause());
    } catch (IllegalAccessException e) {
        throw new IllegalStateException("Unexpected exception", e);
    }
}

ps:【Thread-5, 5, main】:Thread-5是线程名,5:优先级,main: 线程所属线程组

image-20230421174356419.png

适合计算量小的耗时少的、不涉及UI的代码

MAIN

    case MAIN:
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            

subscribe都会在主线程中执行

  1. 如果post是在主线程中,则串行执行invokeSubscriber,因此会依次执行post和subscribe
  2. 如果post在子线程中,则通过enqueue加到主线程的执行队列里面,subscribe会保留相对执行顺序。

修改对应的代码如下:

@Subscribe(threadMode = ThreadMode.MAIN)
    fun onMessageEvent(event: EventBusMessage?) {
        Log.d(TAG, "Receiver Thread: ${Thread.currentThread()} onMessageEvent: ${event!!.name} ")
    }
​
 findViewById.setOnClickListener {
//            Thread{
//                for (i in 0 until 3){
//                    Log.d(TAG, "post Thread: ${Thread.currentThread()}")
//                    EventBus.getDefault().post(EventBusMessage(i))
//                }
//            }.start()
            for (i in 0 until 3){
                Log.d(TAG, "post Thread: ${Thread.currentThread()}")
                EventBus.getDefault().post(EventBusMessage(i))
            }
        }

post在子线程子线程:

image.png

post在主线程:依次执行

image-20230421191203092.png

MAIN_ORDERED

case MAIN_ORDERED:
    if (mainThreadPoster != null) {
        mainThreadPoster.enqueue(subscription, event);
    } else {
        // temporary: technically not correct as poster not decoupled from subscriber
        invokeSubscriber(subscription, event);
    }

无论事件发布者post在什么线程环境,都会将subscribe加到主线程的执行队列,(ps. mainThreadPoster感觉上一直是非null的?)

BACKGROUND

该模式下的时间发布者post线程环境与事件接收onReceiveMsg方法的线程环境关系如下:

case BACKGROUND:
    if (isMainThread) {
        backgroundPoster.enqueue(subscription, event);
    } else {
        invokeSubscriber(subscription, event);
    }
    break;
  1. post在主线程,subscribe在子线程执行
  2. post在子线程,subscribe在同一子线程执行

ASYNC:

case ASYNC:
    asyncPoster.enqueue(subscription, event);

该模式表示,无论post环境是什么线程,事件接收处理环境都是子线程。

ASYNCBACKGROUND有什么区别?

backgroundPoster在 Executor的execute()方法 上添加了 synchronized关键字 并设立了控制标记flag,保证任一时间只且仅能有一个任务会被线程池执行,而ASYNC没有限制,可以同时在不同线程执行不同任务,但是应该是同一线程池。 执行结果如下所示:

image-20230421192240033.png

由于篇幅,完整的代码占篇幅就不贴了,相信认真的读者可以理解。

sticky

sticky默认的设置为false

将sticky设置为true允许先post后subscribe,更改代码如下,注意不再是

EventBus.getDefault().post(EventBusMessage(i))

而是:EventBus.getDefault().postSticky(EventBusMessage(i))

for (i in 0 until 3){
    Log.d(TAG, "post Thread: ${Thread.currentThread()}")
    EventBus.getDefault().postSticky(EventBusMessage(i))
}
​
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    fun onMessageEvent(event: EventBusMessage?) {
        Log.d(TAG, "Receiver Thread: ${Thread.currentThread()} onMessageEvent: ${event!!.name} ")
    }

我们先post然后再注册结果如下:可以看出,只接收到了一个事件,后注册应该只能接收注册之前的最后一个post事件

image-20230421195541258.png

priority

该参数是int型,默认值是0,优先级值越高,越先接收到事件,不过这里要注意一个问题,那就是优先级的比较前提是在post事件发布,onReceiveMsg事件接收处理这两方的线程环境相同的前提下,才有意义。

同是与priority相配合使用的一个方法是cancelEventDelivery.示例如下,如果满足某些事件,取消继续传播

 @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    fun onMessageEvent(event: EventBusMessage?) {
        if(condition()){
            EventBus.getDefault().cancelEventDelivery(event)
        }
    }