这两天对Github上很火的Eventbus进行了学习,查阅了一些博客,写代码做了实验,以此文总结。
本文档前面是对EventBus各方面的介绍。如果要学习快速使用,可以直接跳转到后面的详细使用实例部分。
本文EventBus指的是:greenrobot/EventBus
Github上有很多同名项目,不要搞错。
EventBus是使用观察者模式来替代Intent、Handler、Broadcast的在Activity、Fragment、Service之间传递消息的机制。能降低组件间的耦合,使代码更简洁。
概念图
特点
“事件”是一个POJO,根据自己需要自己随便定义的一个JAVA类,包含需要传递给其他组件的信息。
事件发布者不需要注册,直接使用EventBus.getDefault().post(new TextEvent(“我有一头小毛驴”))。其中TextEvent是一个自定义类。
事件订阅者(接受者、处理者)需要在onCreate()方法中通过EventBus.getDefault().register(this)注册自己,在onDestroy()方法中通过EventBus.getDefault().unregister(this)取消注册。同时,需要至少实现4种onEventXXX方法中的一种。
支持4种事件响应模式:
- MainThread:对应的响应函数写法为onEventMainThread,事件响应函数会在Android应用的主线程(大部分情况下都是UI线程)中执行。
- PostThread:对应的响应函数写法为onEvent,即默认的处理方式就是post方式。事件响应函数和事件发布在同一线程中执行。这个是默认值,这样可以避免线程切换。
- BackgroundThread:对应的响应函数写法为onEventBackgroundThread,事件响应函数会在一个后台线程中执行。如果事件发布函数不是在主线程中,则会立即在事件发布线程中执行响应函数。如果事件发布函数在主线程中,EventBus则会在唯一的一个后台线程中按照顺序来执行所有的后台事件响应函数。
- Async:对应的响应函数写法为onEventAsync,每接收到一个事件都会开启一个异步线程执行。
前3种响应函数都要求其中不能有耗时操作,防止UI更新、事件分发或新事件的处理等操作被阻塞。
通过实验,如果在同一个订阅者中对同一个事件同时注册了这4种响应函数,则控制台打印的时间处理函数顺序为 :
onEvent()->onEventMainThread()->onEventAsync()->onEventBackgroundThread()
或
onEvent()->onEventMainThread()->onEventBackgroundThread()->onEventAsync()。
由于onEventBackgroundThread()和onEventAsync()都是在后台线程执行的,包括在控制台打印函数名的操作,所以此顺序不一定就是这几个函数被触发的顺序。
优先级
EventBus在注册订阅者时需要指定此订阅者的优先级,用整型数表示,数字越大优先级越高。如果不显示指定优先级,则默认为0。优先级可以为负数。
对于同一个事件的多个订阅者,高优先级的订阅者会先接收到Event。
接收到Event的订阅者可以决定是否取消事件的传递,来使后面的订阅者无法接收到Event。方法是在onEvent方法中调用EventBus.getDefault().cancelEventDelivery(event),也就是说,只能在Post线程的onEvent方法中取消事件的传递。
经过实验,相同优先级的订阅者是按照注册的先后顺序来接收事件的。先接收到事件的订阅者如果在onEvent()方法中调用EventBus.getDefault().cancelEventDelivery(event),同样可以中断事件继续传播,但中断得不彻底,紧随其后的1个订阅者仍然有可能接收到事件。
注意:
如果第一个接收到时间的订阅者在任何一个订阅函数中对接收到的event的内容做了修改,那么后续的其他订阅者的所有onEventXXX函数接受到的event也都是被修改的。即post操作只产生一个event对象,所有订阅者的onEventXXX函数接受到的都是同一个对象的引用。
如果event被生产者post之后,订阅者接收到了事件并足够快的对该event的某些字段做了修改,那么生产者在post语句之后对event的引用也会体现出字段的修改。这里的“足够快”没有定论,实验的每次结果都有差异,不过基本的规律是生产者的post()语句之后的下一条语句,会在所有订阅者的onEvent()和onEventMainThread()都执行完成后才执行。而onEventAsync()和onEventBackgroundThread()相对于post()下一条语句的执行顺序则没有定论。
关于cancelEventDelivery的问题
如果在post线程的onEvent方法中,先有一段特别耗时的操作,然后才调用cancelEventDelivery,那么后面的订阅者有可能因为cancelEventDelivery没被即时执行而接收到事件吗?比如订阅者1的onEvent方法是这样的:
1 | public void onEvent(TextEvent event) { |
那么本该在订阅者1后面才接收到事件的订阅者2会接收到event吗?也许EventBus会在for循环这段时间内已近把事件传递给订阅者2了,毕竟这时还没有执行EventBus.getDefault().cancelEventDelivery(event)?
带着这个问题,跟进了一下EventBus的源码,有以下发现。
在事件生产者调用post方法时,Eventbus内部是通过调用这个postSingleEventForEventType
方法来进行消息传递的,一旦EventBus.getDefault().cancelEventDelivery
(event)方法被调用,aborted就被置为false,这个for循环就会停止,消息不会再向后面任何订阅者的任何一种线程函数传递。
由于cancelEventDelivery方法只能在post线程的onEvent函数中调用,而这里的postToSubscription方法对于post线程的执行是同步的,所以即使某个订阅者在post线程的onEvent中先进行耗时操作再调用cancelEventDelivery,后面的订阅者也接受不到这个事件,因为postSingleEventForEventType方法还在等待2返回(同步方法)。
postToSubscription的内部实现如下:
我用代码在各种线程中都试过了,确实不用担心cancelEventDelivery之前的耗时操作导致在耗时操作期间事件仍然被传递的问题。只有一种例外,就是在post线程的onThread方法中再启新线程进行cancelEventDelivery调用,不会这样做没有意义,因为eventbus已经提供了4种线程模型了,不需要这样多此一举, 相信不会有人这样写代码,就像下面这样:
基本用法流程3步搞定
1.定义事件
1 | public class MessageEvent { /* Additional fields if needed */ } |
2.注册订阅者
1 | eventBus.register(this); public void onEvent(AnyEventType event) {/* Do something */}; |
3.发送事件
1 | eventBus.post(event); |
StickyEvent
除了普通的Event,还有一类特殊的StickyEvent,它也是POJO,事件本身和普通事件无区别,只是在注册订阅者注册时需要使用EventBus.getDefault().registerSticky(this)。事件处理函数的函数名也与普通事件的处理函数一样,因为注册为了StickyEvent的监听器,所以EventBus在调用这些事件处理函数时会按照StickyEvent的方式来处理。
StickyEvent与普通Event的普通就在于,EventBus会自动维护被作为StickyEvent被post出来(即在发布事件时使用EventBus.getDefault().postSticky(new MyEvent())方法)的事件的最后一个副本在缓存中。
任何时候在任何一个订阅了该事件的订阅者中的任何地方(可以在任何函数中,而不仅仅是在onEventXXX方法中),都可以通过EventBus.getDefault().getStickyEvent(MyEvent.class)来取得该类型事件的最后一次缓存。
同时,即便事件已经在所有订阅者中传递完成了,如果此时再创建一个新的订阅者(如一个注册了该StickyEvent的Activity),则在订阅者启动后,会自动调用一次该订阅者的noEventXXX方法来处理该StickyEvent。
也可以在需要的时候,利用removeStickyEvent方法来移除对某种StickyEvent的缓存。
网上博文总结EventBus的优点
- 支持在不同类型的线程中处理订阅,包括发布所在线程,UI线程、单一后台线程、异步线程
- 支持事件优先级定义,支持优先级高的订阅者取消事件继续传递,支持粘性事件,是不是跟系统的有序广播、粘性广播很像啊
- 不是基于annotations
- 性能更优
- 体积小
- 支持单例创建或创建多个对象
- 支持根据事件类型订阅
详细使用实例
1.准备工作:
在项目build.gradle的dependencies中加入以下内容:1
compile 'de.greenrobot:eventbus:2.4.0'
2.编写事件
1 | public class TextEvent { |
3.在需要订阅该事件的Activity的onCreate()方法中,把自己注册为订阅者。
默认使用EventBus提供的默认总线,即EventBus.getDefault()方法获得的总线,其内部实现了单例模式。自己也可以根据需要定制不同的总线。
register()方法是向总线订阅普通事件,registerSticky方法是向总线订阅Sticky事件。只能使用二者当中的一种方法。
1 |
|
4.在注册为订阅者的Activity的onDestroy()方法中,解除注册。
1 |
|
5.在注册为订阅者的Activity中,编写事件处理方法。
下面的代码列举了4种线程类型的事件处理方法,实际使用时可以根据需要只实现其中的几种。
1 | public void onEventAsync(TextEvent event){ |
6.在需要产生事件的组件(Activity、Fragment、Service等)中,调用发送事件的方法。
下面的代码列举了发送普通事件和发送Sticky事件两种情况。
1 |
|
这样,当点击按钮触发onClick()时,就能在第5步的MainActivity中的onEventXXX方法中接收到事件了。
如果发送的是Sticky事件,则可以在任何需要获取该事件的地方通过EventBus.getDefault().getStickyEvent(TextEvent.class)方法来获取该类型的StickyEvent的最后一次数据。
参考资料