0001B
时近年末,但是也没闲着。最近正好在看 EventBus 的源码。那就正好今天来说说 EventBus 的那些事儿。
EventBus 是什么呢(相信地球人都知道→_→)?
EventBus is a publish/subscribe event bus optimized for Android.
这是官方给的介绍,简洁、明了、霸气。翻译过来就是:EventBus 是一种为 Android 而优化设计的发布/订阅事件总线。这官方的套词可能有些人看了还是不懂。。。

简单地举了栗子,EventBus 就好像一辆公交车(快上车,老司机要飙车 乀(ˉεˉ乀) )。相对应的,发布事件就可以类比为乘客,订阅事件就好似接站服务的人。乘客想要到达指定目的地就必须上车乘坐该公交车,公交车会做统一配置管理每位乘客(发布事件流程)。达到目的地后,打开下车门,把乘客交任给接站服务的人做相应的处理(订阅事件流程)。不知道这个栗子你们懂不懂,反正我是懂了( ̄ε  ̄)。

所以总的来说,对于一个事件,你只要关心发送和接收就行了,而其中的收集、分发等都交给 EventBus 来处理,你不需要做任何事。不得不说这太方便了,能让代码更见简洁,大大降低了模块之间的耦合性。
0002B 使用方法
现在,来看一下 EventBus 的使用方法,直接复制粘贴 GitHub 中的例子:
第一步,定义一个事件类
MessageEvent:public static class MessageEvent { /* Additional fields if needed */ }定义一个订阅方法,可以使用
@Subscribe注解来指定订阅方法所在的线程:@Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { /* Do something */ };注册和反注册你的订阅方法。比如在 Android 中,Activity 和 Fragment 通常在如下的生命周期中进行注册和反注册:
@Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }
3.发送事件:
EventBus.getDefault().post(new MessageEvent());
可以看出 EventBus 使用起来很简单,就这么几行代码解决了许多我们备受困扰的问题。那么接下来我们就深入 EventBus 的源码内部,一探究竟。
0003B EventBus
在 GitHub 上对于 EventBus 整体有一张示意图,很明确地画出了整个框架的设计原理:

那么依据这张图,我们先从 “Publisher” 开始讲起吧。PS : 本文分析的 EventBus 源码版本为 3.0.0 。
EventBus.getDefault()
来看一下 EventBus.getDefault() 的源码(文件路径:org/greenrobot/eventbus/EventBus.java):
1 | private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder(); |
从上面的源码中可以看出,平时的我们经常调用的 EventBus.getDefault() 代码,其实是获取了 EventBus 类的单例。若该单例未实例化,那么会根据 DEFAULT_BUILDER 采用构造者模式去实例化该单例。在 EventBus 构造器中初始化了一堆的成员变量,这些都会在下面中使用到。
register(Object subscriber)
事件订阅者必须调用 register(Object subscriber) 方法来进行注册,一起来看看在 register(Object subscriber) 中到底做了一些什么:
1 | public void register(Object subscriber) { |
在 register(Object subscriber) 中,利用 subscriberMethodFinder.findSubscriberMethods 方法找到订阅者 class 下所有的订阅方法,然后用 for 循环建立订阅关系。其中 subscriberMethodFinder.findSubscriberMethods 方法我们暂时先不看了,跳过。在这里只要知道作用是找到该订阅者所有的订阅方法就好了。具体 SubscriberMethodFinder 的代码会在后面的章节中详细分析。
而 SubscriberMethod 其实就是订阅方法的包装类:
1 | public class SubscriberMethod { |
然后就是轮到了 subscribe(subscriber, subscriberMethod) 方法:
1 | private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { |
其实 subscribe(subscriber, subscriberMethod) 方法主要就做了三件事:
- 得到
subscriptions,然后根据优先级把subscriberMethod插入到subscriptions中; - 将
eventType放入到subscribedEvents中; - 如果订阅方法支持
sticky,那么发送相关的粘性事件。
粘性事件发送调用了 checkPostStickyEventToSubscription(newSubscription, stickyEvent); 。从方法的命名上来看,知道应该是事件发送到订阅者相关的代码。那么继续跟进代码:
1 | private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) { |
在 checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) 方法的内部调用了 postToSubscription(Subscription subscription, Object event, boolean isMainThread) 。主要的操作都在 postToSubscription 中。根据 threadMode 共分为四种:
- 同一个线程:表示订阅方法所处的线程和发布事件的线程是同一个线程;
- 主线程:如果发布事件的线程是主线程,那么直接执行订阅方法;否则利用 Handler 回调主线程来执行;
- 子线程:如果发布事件的线程是主线程,那么调用线程池中的子线程来执行订阅方法;否则直接执行;
- 异步线程:无论发布事件执行在主线程还是子线程,都利用一个异步线程来执行订阅方法。
这四种线程模式其实最后都会调用 invokeSubscriber(Subscription subscription, Object event) 方法通过反射来执行。至此,关于粘性事件的发送就告一段落了。
另外,在这里因篇幅原因就不对 mainThreadPoster 和 backgroundPoster 等细说了,可以自行回去看相关源码,比较简单。
unregister(Object subscriber)
看完 register(Object subscriber) ,接下来顺便看看 unregister(Object subscriber) 的源码:
1 | public synchronized void unregister(Object subscriber) { |
瞟了一眼 unregister(Object subscriber) 方法,我们基本上就已经知道其中做了什么。在之前 register(Object subscriber) 中 subscriptionsByEventType 和 typesBySubscriber 会对 subscriber 间接进行绑定。而在 unregister(Object subscriber) 会对其解绑,这样就防止了造成内存泄露的危险。
post(Object event)
最后,我们来分析下发送事件 post(Object event) 的源码:
1 | private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() { |
在 post(Object event) 中,首先根据 currentPostingThreadState 获取当前线程状态 postingState 。currentPostingThreadState 其实就是一个 ThreadLocal 类的对象,不同的线程根据自己独有的索引值可以得到相应属于自己的 postingState 数据。
然后把事件 event 加入到 eventQueue 队列中排队。只要 eventQueue 不为空,就不间断地发送事件。而发送单个事件的代码在 postSingleEvent(Object event, PostingThreadState postingState) 中,我们跟进去看:
1 | private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { |
postSingleEvent(Object event, PostingThreadState postingState) 中的代码逻辑还是比较清晰的,会根据 eventInheritance 分成两种:
- 支持事件继承:得到
eventClass的所有父类和接口,然后循环依次发送事件; - 不支持事件继承:直接发送事件。
另外,若找不到订阅者,在默认配置下还会发送 NoSubscriberEvent 事件。需要开发者自定义订阅方法接收这个事件。
关于发送的具体操作还是要到 postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) 中去看:
1 | private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { |
仔细看上面的代码,我们应该能发现一个重要的线索—— postToSubscription 。没错,就是上面讲解发送粘性事件中的 postToSubscription 方法。神奇地绕了一圈又绕回来了。
而 postSingleEventForEventType 方法做的事情只不过是遍历了订阅者,然后一个个依次调用 postToSubscription 方法,之后就是进入 switch 四种线程模式(POSTING 、MAIN 、BACKGROUND 和 ASYNC)并执行订阅者的订阅方法的逻辑了。这里就不重复讲了,具体可以查看上面发送粘性事件中的分析。
至此,整个 EventBus 发布/订阅的原理就讲完了。EventBus 是一款典型的运行观察者模式的开源框架,设计巧妙,代码也通俗易懂,值得我们学习。
别以为到这里就本文结束了,可不要忘了,在前面我们还留下一个坑没填—— SubscriberMethodFinder 。想不想知道 SubscriberMethodFinder 到底是如何工作的呢?那还等什么,我们赶快进入下一章节。
0004B SubscriberMethodFinder
SubscriberMethodFinder 的作用说白了其实就是寻找订阅者的订阅方法。正如在上面的代码中提到的那样, findSubscriberMethods 方法可以返回指定订阅者中的所有订阅方法。
findSubscriberMethods(Class<?> subscriberClass)
我们看下内部的源码(文件路径:org/greenrobot/eventbus/SubscriberMethodFinder.java):
1 | List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { |
内部有两种途径获取:findUsingReflection(Class<?> subscriberClass) 和 findUsingInfo(Class<?> subscriberClass) 。另外,还有缓存可以提高索引效率。
findUsingReflection(Class<?> subscriberClass)
那么我们先来看看 findUsingReflection(Class<?> subscriberClass) 方法:
1 | private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) { |
这里出现一个新的类 FindState ,而 FindState 的作用可以对订阅方法做一些校验,以及查找到的所有订阅方法也是封装在 FindState.subscriberMethods 中的。另外,在 SubscriberMethodFinder 类内部还维持着一个 FIND_STATE_POOL ,可以循环利用,节省内存。
接着往下看,就发现了一个关键的方法: findUsingReflectionInSingleClass(FindState findState) 。根据这方法名可以知道反射获取订阅方法的操作就在这儿:
1 | private void findUsingReflectionInSingleClass(FindState findState) { |
通过一个个循环订阅者中的方法,筛选得到其中的订阅方法后,保存在 findState.subscriberMethods 中。最后在 getMethodsAndRelease(FindState findState) 方法中把 findState.subscriberMethods 返回。(这里就不对 getMethodsAndRelease(FindState findState) 做解析了,可以下去自己看代码,比较简单 *^ο^* )
findUsingInfo(Class<?> subscriberClass)
最后,剩下另外一种获取订阅方法的途径还没讲。
1 | private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { |
我们发现在 findUsingInfo(Class<?> subscriberClass) 中是通过 SubscriberInfo 类来获取订阅方法的;如果没有 SubscriberInfo ,就直接通过反射的形式来获取。那么 SubscriberInfo 又是如何得到的呢?还要继续跟踪到 getSubscriberInfo(FindState findState) 方法中。然后又有一个新的类蹦出来—— SubscriberInfoIndex 。那么 SubscriberInfoIndex 又是什么东东啊(文件路径:org/greenrobot/eventbus/meta/SubscriberInfoIndex.java)?
1 | public interface SubscriberInfoIndex { |
点进去后发现 SubscriberInfoIndex 只是一个接口而已,是不是感到莫名其妙。What the hell is it!
我们把这个疑问先放在心里,到 EventBusPerformance 这个 module 中,进入 build/generated/source/apt/debug/org/greenrobot/eventbusperf 目录下,发现有一个类叫 MyEventBusIndex :
1 | /** This class is generated by EventBus, do not edit. */ |
从代码中可知,MyEventBusIndex 其实是 SubscriberInfoIndex 的实现类,并且是 EventBus 自动生成的(根据注释可知这点)。而 getSubscriberInfo(Class<?> subscriberClass) 方法已经实现了,内部维持着一个 SUBSCRIBER_INDEX 的 HashMap ,用来保存订阅类的相关信息 info 。然后在需要的时候可以通过 info 快速返回 SubscriberMethod 。这样就达到了不用反射获取订阅方法的目的,提高了执行效率。
到了这里我们明白了上面关于 SubscriberInfoIndex 的疑问,但是又有一个新的疑问产生了:MyEventBusIndex 到底是如何生成的?想要解开这个疑问,我们就要去 EventBusAnnotationProcessor 类中寻找答案了。
0005B EventBusAnnotationProcessor
一看到 EventBusAnnotationProcessor ,菊花一紧,料想肯定逃不了注解。我们可以猜出个大概: EventBus 在编译时通过 EventBusAnnotationProcessor 寻找到所有标有 @Subscribe 注解的订阅方法,然后依据这些订阅方法自动生成像 MyEventBusIndex 一样的索引类代码,以此提高索引效率。
总体来说,这种注解的思路和 Dagger 、ButterKnife 等框架类似。想要了更多,可以阅读我的上一篇博客《ButterKnife源码分析》。
在这里由于篇幅的原因只能简单粗略地解析 EventBusAnnotationProcessor 的源码了,还请多多谅解。
process(Set<?extendsTypeElement> annotations, RoundEnvironment env)
我们简单地来分析一下 process(Set<? extends TypeElement> annotations, RoundEnvironment env) :
1 | @Override |
其实在 process(Set<? extends TypeElement> annotations, RoundEnvironment env) 方法中重要的代码就这么几行,其他不重要的代码都省略了。那现在我们顺着一个一个方法来看。
collectSubscribers(Set<?extendsTypeElement> annotations, RoundEnvironment env, Messager messager)
我们先从 collectSubscribers(annotations, env, messager); 开始入手:
1 | private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) { |
上面代码做的事情就是根据注解获取了对应的方法,然后初步筛选了一些方法,放入 methodsByClass 中。
checkForSubscribersToSkip(Messager messager, String myPackage)
得到这些初选的订阅方法后,就要进入 checkForSubscribersToSkip(Messager messager, String myPackage) 环节:
1 | private void checkForSubscribersToSkip(Messager messager, String myPackage) { |
用一句话来概括,checkForSubscribersToSkip(Messager messager, String myPackage) 做的事情就是如果这些订阅类中牵扯到不可见状态,那么就会被加入到 classesToSkip 中,导致后面生成索引类中跳过这些订阅类。
createInfoIndexFile(String index)
经过筛选后,EventBusAnnotationProcessor 最终要生成一个索引类,具体的代码就在 createInfoIndexFile(String index) 中:
1 | private void createInfoIndexFile(String index) { |
上面的这几行代码应该很眼熟吧,MyEventBusIndex 就是从这个模子里“刻”出来的,都是写死的代码。不同的是在 writeIndexLines(BufferedWriter writer, String myPackage) 中会把之前包含在 classesToSkip 里的跳过,其他的都自动生成 index 。最后就能得到一个像 MyEventBusIndex 一样的索引类了。
另外补充一句,如果你想使用像 MyEventBusIndex 一样的索引类,需要在初始化 EventBus 时通过 EventBus.builder().addIndex(new MyEventBusIndex()).build(); 形式来将索引类配置进去。
话已至此,整个 EventBusAnnotationProcessor 我们大致地分析了一遍。利用编译时注解的特性来生成索引类是一种很好的解决途径,避免了程序在运行时利用反射去获取订阅方法,提高了运行效率的同时又提高了逼格。
0006B 总结
从头到尾分析下来,发现 EventBus 真的是一款不错的开源框架,完美诠释了观察者模式。从之前的 2.0 版本到现在的 3.0 版本,加入了注解的同时也减少了反射,提高了性能,为此增添了不少的色彩。
与 EventBus 相似的还有 Otto 框架,当然现在业内也有不少使用 RxJava 来实现具备发布/订阅功能的 “RxBus” 。对此我的看法是,如果是小型项目,可以使用 RxBus 来代替 EventBus ,但是一旦项目成熟起来,涉及到模块之前通信和解耦,那么还是使用更加专业的 EventBus 吧。毕竟若是新手想上手 RxJava 还是需要一段时间的。
今天就到这了,对 EventBus 有问题的同学可以留言,bye bye !