基本介绍
EventBus,中文名叫事件总线,他的定义如下:
Android optimized event bus that simplifies communication between Activities, Fragments, Threads, Services, etc.
可以看出,EventBus能够简化同一个进程的组件(比如Activity, Fragment, 线程, Service)之间的通信。
EventBus只支持同一个进程内的通信,如果要跨进程通信,请使用HermesEventBus。
2016年2月份发布了EventBus 3.0,相比原来有两大优化点:
- EventBus 2.x中,订阅方法的方法名一定要以onEvent开头,比如onEventMainThread表示订阅方法执行在主线程;而EventBus 3.0中,订阅方法的方法名随意,只需要在前面加上注解:@Subscribe。
- 通过注解方式声明订阅方法,速度相比2.x会变慢,通过引入注解处理器(annotation processor),在编译期间建立订阅方法的索引,性能有明显提升。
根据官方说明,注解的引入让EventBus 3.0相比2.x性能变差3-5倍,但是引入索引,3.0相比2.x性能提高至少3倍。
如果不引入索引,只需要在build.gradle加入:
|
在proguard添加:
|
但是引入索引能够大幅提高性能,因此一般建议加入,在build.gradle加入:
|
然后在Application的onCreate()中加入:
|
编译之后,索引文件在build/generated/source/apt/debug/com/xiazdong/eventbusdemo/EventBusIndex.java
。
基本使用
EventBus的使用一共3个步骤:
- 定义事件(Event),事件就是EventBus中相互传递的对象(类似于Intent中的Bundle)。
- 发送事件(Publisher)。
- 订阅事件,实现订阅方法(Subscriber)。
首先是定义事件:
|
这样事件传递的对象就是MessageEvent,里面包含name和age。
接着,发送事件:
|
这个post()方法可以在任意线程调用。
EventBus还支持粘性事件,一般来说,在post()之前订阅该事件才会收到事件,但是粘性事件为:发送事件(postSticky())之后再订阅事件也能收到该事件。
最后订阅事件:
|
onMessageReceived()就是订阅方法,threadMode有四种取值:
- ThreadMode.POSTING:默认值,post()在哪个线程调用,订阅方法就执行在哪个线程,不能执行耗时操作。
- ThreadMode.MAIN:订阅方法执行在主线程,用于更新UI。
- ThreadMode.BACKGROUND:如果post()在主线程调用,则订阅方法执行在某个固定后台线程(标记为BACKGROUND的订阅方法都执行在该线程);如果post()在子线程调用,则订阅方法在该线程调用。不能执行耗时操作。
- ThreadMode.ASYNC:新建线程执行订阅方法,可以执行耗时操作。
priority是优先级,值越大优先级越高,默认值为0。
如果是粘性事件,需要加:sticky = true。
EventBus这种通信方式的好处是:传递的内容可以是任意的,不像Intent中的Bundle只支持基本类型和实现了Parcelable的类型。
LocalBroadcastManager(本地广播)是Android Support包提供的类,用于同一进程内的通信,是阉割版的EventBus,性能和加入编译时索引的EventBus差不多,相比EventBus有几个缺点:
- 通过Intent的Bundle传递数据,数据类型只能是基本类型或实现Parcelable接口。EventBus能传递任何对象。
- onReceive()只能在主线程运行。EventBus的订阅方法能通过配置运行在主线程或子线程。
- 使用麻烦。
因此建议使用EventBus。
基本原理
我们先从编译时生成的索引类(这里类名为EventBusIndex)开始讲起,该类是的作用是:收集所有订阅方法的信息,并保存到SUBSCRIBER_INDEX变量中,该变量是Map,key为类名,value为SubscriberInfo对象(实际为实现SubscriberInfo接口的SimpleSubscriberInfo对象)。需要注意:
- SimpleSubscriberInfo对象包含了:类名、该类中所有的订阅方法(SubscriberMethodInfo对象)、该类的父类的SubscriberInfo对象。
- 一个SubscriberMethodInfo对象包含了一个订阅方法的信息,包含了方法名、threadMode值、事件类型(即订阅方法的参数类型)、priority值、sticky值。
- EventBusIndex的getSubscriberInfo()能通过订阅者类返回SubscriberInfo对象。
我们先分析Application中的语句:
|
EventBus类是核心类,整个原理的分析全部围绕他,EventBusBuilder类是构造EventBus对象的类。通过为EventBusBuilder设置一些配置(比如是否需要忽略索引类,即ignoreGeneratedIndex变量,默认为false),然后调用build()就构造出了一个EventBus对象。
EventBus.builder()创建了EventBusBuilder对象,addIndex(new EventBusIndex())将索引类对象设置到EventBusBuilder对象的subscriberInfoIndexes变量中,installDefaultEventBus()为根据EventBusBuilder的配置构建一个EventBus对象,并设置到EventBus类的defaultInstance变量中。
未来的EventBus.getDefault()都会获得Application中配置的新的EventBus对象。
EventBus整个注册、发送、接收过程,最关键的变量为:
Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType
:key为事件类型,value为List<Subscription>
,即该事件类型对应的所有订阅方法(顺序按优先级排序),Subscription类包含了订阅者类和一个SubscriberMethod对象(包含了订阅方法的信息,和SubscriberMethodInfo差不多)。Map<Object, List<Class<?>>> typesBySubscriber
:key为订阅者类名,value为该订阅者类中所有的事件类型。Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE
:key为订阅者类名,value为该类中所有的订阅方法信息。这是缓存类,用于缓存通过索引或反射查找到的结果。SubscriberMethodFinder subscriberMethodFinder
:其中的findSubscriberMethods()根据订阅者类获得该类中所有的订阅方法信息。
注册:即调用EventBus的register(),即注册订阅者的所有订阅事件,代码如下:
|
该方法先通过subscriberMethodFinder的findSubscriberMethods()根据订阅者类名查找到该类的所有订阅方法信息(List<SubscriberMethod>
),然后调用subscribe()把这些信息填充到subscriptionsByEventType和typesBySubscriber中。
SubscriberMethodFinder类中的findSubscriberMethods()代码如下:
|
该方法先后通过几种方法查找订阅方法信息:
- METHOD_CACHE:存放以前查找过的结果,避免下次重复查找。
- 判断ignoreGeneratedIndex:是否忽略生成的索引类,该值默认为false,也建议为false,因此我们只考虑else分支。
findUsingInfo()
:先从索引类中查找结果,如果找不到,再通过反射(findUsingReflectionInSingleClass()
)查找结果。- 如果查找到结果,就放入METHOD_CACHE缓存,避免下次重复查找。
因此索引类的引入主要减少了register()的耗时,即用编译时生成索引的方式代替了方法反射,前面说的EventBus 3.0相比EventBus 2.x速度提升至少3倍说的就是register()方法速度的提升。
接着看unregister(),该方法就是将该订阅者相关的订阅事件信息和订阅方法信息等从subscriptionsByEventType和typesBySubscriber中删除。
最后是post(),该方法用来发送订阅事件,并调用相关订阅者的订阅方法,具体怎么查找订阅者,是通过subscriptionsByEventType查找订阅者的订阅方法。具体在哪个线程调用订阅方法,通过订阅方法的threadMode声明确定。post()方法实现如下:
|
currentPostingThreadState变量是ThreadLocal<PostingThreadState>
类型的,即每个线程都有一个PostingThreadState对象,该类结构为:
|
post()共分为3步:
- 把订阅事件信息放入eventQueue中,等待被执行。
- 判断isPosting变量是否为true(即当前线程是否正在发送事件),如果为false,则置为true并循环遍历eventQueue,并调用postSingleEvent()发送订阅事件;否则直接返回,让当前正在遍历队列的对象执行订阅方法。
- 最后把isPosting和isMainThread置为false。
postSingleEvent()首先会寻找该订阅事件的所有父类,并依次发送这些事件和该订阅事件,即对每个订阅事件调用postSingleEventForEventType(),在postSingleEventForEventType()中通过subscriptionsByEventType查找到订阅事件对应的所有订阅方法,并对每个订阅方法调用postToSubscription(),该方法是EventBus调配订阅方法在哪个线程执行的核心方法,如下:
|
这里有三种Poster,他们的共同点:内部都有PostPendingQueue和PendingPost,PostPendingQueue是存放PendingPost的队列,PendingPost存放了订阅事件和Subscription对象,即待调用的订阅方法。
他们的不同点:
- mainThreadPoster是主线程的Handler,即在handleMessage中调用订阅方法;backgroundPoster和asyncPoster是Runnable,他们的run()会遍历PostPendingQueue队列,并调用订阅方法。
- backgroundPoster:和asyncPoster一起使用EventBus的executorService线程池。内部有executorRunning变量判断run()方法是否正在执行,如果正在执行,就只是把PendingPost放入队列,让当前的run()方法去调用该订阅方法。
- asyncPoster:使用EventBus的executorService线程池,每次都创建线程并调用订阅方法。
对于threadMode的值分了四种情况:
- POSTING:invokeSubscriber()就是直接用反射调用订阅方法。
- MAIN:如果调用post()时就是主线程,那就直接反射调用订阅方法;否则就加入mainThreadPoster的队列。
- BACKGROUND:如果当前线程是主线程,那么加入backgroundPoster的队列;如果当前线程不是主线程,那么直接调用订阅方法。
- ASYNC:加入asyncPoster的队列。
参考文献
- 入门:http://liuwangshu.cn/application/eventbus/1-eventbus.html
- 原理1:http://blog.csdn.net/u011240877/article/details/73196808
- 原理2:http://www.cnblogs.com/bugly/p/5475034.html
- 原理3:http://liuwangshu.cn/application/eventbus/2-eventbus-sourcecode.html
- 官方PPT:http://androiddevblog.com/wordpress/wp-content/uploads/EventBus3.pdf