前言
EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是使用观察者模式替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。
- 支持在不同类型的线程中处理订阅,包括发布所在线程,UI线程、单一后台线程、异步线程
- 支持事件优先级定义,支持优先级高的订阅者取消事件继续传递,支持粘性事件,跟系统的有序广播、粘性广播很像
- 不是基于annotations
- 性能更优
- 体积小
- 支持单例创建或创建多个对象
- 支持根据事件类型订阅
概念图
特点
“事件”是一个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种响应函数,则控制台打印的时间处理函数顺序为 :
或
由于onEventBackgroundThread()和onEventAsync()都是在后台线程执行的,包括在控制台打印函数名的操作,所以此顺序不一定就是这几个函数被触发的顺序。
基本流程
1、下载EventBus的类库: EventBus源码下载
2、基本使用
(1)自定义一个类,可以是空类,比如:
(2)在要接收消息的页面注册:
(3)发送消息
(4)接受消息的页面实现(共有四个函数,各功能不同,这是其中之一,可以选择性的实现,这里先实现一个):
(5)解除注册
以上就是使用的基本流程和步骤,下面举个例子来说明下。
首先,在EventBus中,获取实例的方法一般是采用EventBus.getInstance()来获取默认的EventBus实例,当然你也可以new实现,个人感觉还是用默认的比较好,以防出错。
使用示例
当击btn_try按钮的时候,跳到第二个Activity,当点击第二个activity上面的First Event按钮的时候向第一个Activity发送消息,当第一个Activity收到消息后,一方面将消息Toast显示,一方面放入textView中显示。
按照下面的步骤,下面来建这个工程:
1、基本框架搭建
想必大家从一个Activity跳转到第二个Activity的程序应该都会写,这里先稍稍把两个Activity跳转的代码建起来。后面再添加EventBus相关的玩意。
MainActivity布局(activity_main.xml)
|
|
新建一个Activity,SecondActivity布局(activity_second.xml)
|
|
MainActivity.java (点击btn跳转到第二个Activity)
到这,基本框架就搭完了,下面开始按步骤使用EventBus了。
2、新建一个类FirstEvent
|
|
这个类很简单,构造时传进去一个字符串,然后可以通过getMsg()获取出来。
3、在要接收消息的页面注册EventBus:
在上面的GIF图片的演示中,大家也可以看到,我们是要在MainActivity中接收发过来的消息的,所以我们在MainActivity中注册消息。
通过我们会在OnCreate()函数中注册EventBus,在OnDestroy()函数中反注册。所以整体的注册与反注册的代码如下:
|
|
4、发送消息
发送消息是使用EventBus中的Post方法来实现发送的,发送过去的是我们新建的类的实例!
|
|
完整的SecondActivity.java的代码如下:
|
|
5、接收消息
接收消息时,我们使用EventBus中最常用的onEventMainThread()函数来接收消息,具体为什么用这个,我们下篇再讲,这里先给大家一个初步认识,要先能把EventBus用起来先。
在MainActivity中重写onEventMainThread(FirstEvent event),参数就是我们自己定义的类:
在收到Event实例后,我们将其中携带的消息取出,一方面Toast出去,一方面传到TextView中;
完整的MainActiviy代码如下:
|
|
好了,到这,基本上算初步把EventBus用起来了,接下来讲讲EventBus的几个函数,及各个函数间是如何识别当前如何调用哪个函数的。
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的缓存。
订阅函数
- onEvent
- onEventMainThread
- onEventBackgroundThread
- onEventAsync
这四种订阅函数都是使用onEvent开头的,它们的功能稍有不同,在介绍不同之前先介绍两个概念:
告知观察者事件发生时通过EventBus.post函数实现,这个过程叫做事件的发布,观察者被告知事件发生叫做事件的接收,是通过下面的订阅函数实现的。
onEvent:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。
onEventMainThread:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。
onEventBackground:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。
onEventAsync:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.
函数分析
1、解析
上面列出的这四个函数,关键问题在于,我们怎么指定调用哪个函数呢?
我们先研究一下,上一篇中是怎么调用的onEventMainThread函数,除了在接收端注册与反注册以后,关键问题在于新建的一个类:
|
|
发送时:
接收时:
发现什么问题了没?
没错,发送时发送的是这个类的实例,接收时参数就是这个类实例。
所以当发过来一个消息的时候,EventBus怎么知道要调哪个函数呢,就看哪个函数传进去的参数是这个类的实例,哪个是就调哪个。那如果有两个是呢,那两个都会被调用!
为了证明这个问题,下面写个例子,先看下效果。
2、实例
这次我们在上一篇的基础上,新建三个类:FirstEvent、SecondEvent、ThirdEvent,在第二个Activity中发送请求,在MainActivity中接收这三个类的实例,接收时的代码为:
|
|
使用两个onEventMainThread分别接收FirstEvent实例的消息和SecondEvent实例的消息,使用onEvent接收ThirdEvent实例的消息。界面操作及结果如下:
Log输出结果:
可以看到,在发送FirstEvent时,在MainActiviy中虽然有三个函数,但只有第一个onEventMainThread函数的接收参数是FirstEvent,所以会传到它这来接收。所以这里识别调用EventBus中四个函数中哪个函数,是通过参数中的实例来决定的。
因为我们是在上一篇例子的基础上完成的,所以这里的代码就不详细写了,只写改动的部分。
1、三个类
|
|
|
|
|
|
2、发送
然后在SecondActivity中新建三个按钮,分别发送不同的类的实例,代码如下:
3、接收
在MainActivity中,除了注册与注册,我们利用onEventMainThread(FirstEvent event)来接收来自FirstEvent的消息,使用onEventMainThread(SecondEvent event)接收来自SecondEvent 实例的消息,使用onEvent(ThirdEvent event) 来接收ThirdEvent 实例的消息。
到这里,代码就结束 了,从上面的代码,我们可以看到,EventBus是怎么接收消息的,是根据参数中类的实例的类型的判定的,所以当如果我们在接收时,同一个类的实例参数有两个函数来接收会怎样?答案是,这两个函数都会执行,下面实验一下:
在MainActivity中接收时,我们在接收SecondEvent时,在上面onEventMainThread基础上另加一个onEventBackgroundThread和onEventAsync,即下面的代码:
|
|
完整的代码在这里:
|
|
经过上面的分析,当发送SecondEvent实例的消息过来的时候,这三个函数会同时接收到并各自执行,所以当点击Second Event这个button的时候,会出现下面的结果:
结论:消息的接收是根据参数中的类名来决定执行哪一个的。
附加说明
事件总线的模式利用订阅/发布的模式,降低了耦合度的框架除了EventBus,另外还有square的otto和android support包里提供的LocalBroadcast,他们使得两个类没有直接的依赖关系,对架构来说进行了解耦,把双向依赖变成了单向依赖,在大型项目中尤其显得重要。
Why publish/subscribe
一方面,callback很容易产生内存泄露,如I/O、网络操作持有了Activity/Fragment的引用,导致不能及时释放,而团队中也很难保证每个成员都足够优秀在写callback的时候能使用弱引用或静态变量。相比起来订阅/发布者模式则比较简单,直接在BaseActivity的onDestroy释放掉,避免了可能的坑。另外,从可扩展性、可维护性的角度来说,callback也更局限,比如以前某个接口是告诉上层网络数据拉回来了,现在我们希望扩展,这个接口也支持直接告诉上层数据库拉回来了,向上层屏蔽数据来源,如果用callback,则在一次回调结束后,没有办法再次进行回调了。页面必须自己去处理数据来源和拉数据的逻辑。
虽然有些over-architect的嫌疑,但是Android-CleanArchitecture 确实是一种很clean的architecture,而其也正是通过观察者/订阅者模式来实现了单向依赖。
比较
EventBus的github上就有其和otto的比较: EventBus vs Otto
总的来说,两者功能差的不多,otto多了Event producers (e.g. for coding cached events),而EventBus则多了各种线程的处理、订阅者继承、sticky event等。
但从性能来说,由于otto使用了基于反射的annotation,而和EventBus产生了一定的差距。
三者都不支持跨进程,LocalBroadcast内部其实使用的是Handler,所以其实更像是一个utils类。
如果要做选择的话,LocalBroadcast更轻量,otto相比起api更好用,而EventBus则拥有很棒的线程模型。