0. 基础
EventBus 是一个基于事件的“发布-订阅”模式的事件总线框架,有以下基础的概念:
- 订阅者(Subscriber):订阅消息的角色;
- 发布者(Publisher):发布事件的角色;
- 事件(Event):代表一个可以被发布的消息;
- 事件总线(EventBus):用于管理订阅关系和消息的发布的总线;
引用 EventBus官网上关于“发布-订阅” 模式的经典示意图:
从上图中可以看出,整个模式是围着着“事件”的传递进行的,一个事件从发布者,经由总线调度后,被分发给多个订阅者,然后完成整个过程。这个也被称为“生产者-消费者” 模式。
可见该模式的步骤如下:
- 预先定义一些事件;
订阅者去总线那里订阅一些自己关注的事件;- 当某些条件满足后,
发布者通过总线发布事件; - 订阅者收到事件后进行各自相应的处理;
- 如果订阅者不再对事件感兴趣的话,就去总线处取消订阅,后续的事件也将不会再通知到这个订阅者。
1. 源码分析
“订阅-发布” 模式中各个角色的实现:
| 角色 | 实现 | 说明 |
|---|---|---|
| 订阅者(Subscriber) | SubscriberMethod | 订阅者是一个个普通的类,实际上是以方法为单位作为真正的订阅者的。SubscriberMethod订阅方法,代表在EventBus中注册的一个方法 |
| 发布者(Publisher) | EventBus | 在EventBus的框架中,是由EventBus这个类来“兼任“了消息发布者的角色 |
| 事件(Event) | 一些预先定义的POJO格式的java类,也是普通的Java类 | 在EventBus中是用一个一个的类来描述事件的,根据类的Class对象类区分 |
| 事件总线(EventBus) | EventBus类 | 主要的API类,包含有总线的职责 |
其他一些关键类:
- Subscription:订阅信息,内部包含有订阅者和订阅方法的信息;
- EventBusBuilder:用于构造一个EventBus 对象的Builder类;
- SubscriberMethodFinder:用于在类中通过反射来查找某个注解的方法;
EventBus类是整个框架的接口调用类,是使用Builder 设计模式来进行对象的构造的,通过EventBusBuilder类来传入各种参数。各个参数参考官方的说明:EventBus配置
1.1 订阅过程
EventBus框架是使用Java注解语法来实现的,这样做是为了使用的简单,只需要在订阅者类的内部给一些方法加上注解,如下:
在注解添加之后,需要给该方法(订阅者)进行注册,在注册之后,一个订阅者的订阅过程就完成了。事件的订阅过程就是从EventBus.register(Object subscriber) 订阅方法开始的。register 这个方法的内容很简单:
|
|
那么,继续跟踪subscribe(Object subscriber, SubscriberMethod subscriberMethod),能够看到最终的订阅过程,是把一个新的订阅放在类EventBus 的 subscriptionsByEventType 成员中了,该成员是一个Map对象,里面存放的是EventType作为Key,List
|
|
以上就是订阅过程的源码,总的来说,分为以下几个步骤(在调用 EventBus.register 之后):
- 通过反射查找注册的订阅者对象中的订阅方法(SubscriberMethod)的列表;
- 把订阅方法列表中的
SubscriberMethod对象逐个调用subscribe方法;
2.1. 在subscribe方法中,先通过 SubscriberMethod 对象构造出一个Subscription对象来;
2.2. 在subscribe方法中,把构造出的Subscription对象依次正确地放在EventBus 的subscriptionsByEventType和subscribedEvents两个成员中,这两个成员都是Map类型的集合,存放的是已订阅的订阅信息;
2.3. 在subscribe方法中,如果注册的是粘性事件,处理粘性事件的特殊逻辑;
总的来说,就是收集订阅信息,把他们放在EventBus 的subscriptionsByEventType 和 subscribedEvents 两个成员中。
以上,就完成了订阅的流程。
1.2 取消订阅过程
取消发布的流程,是通过调用EventBus 的public synchronized void unregister(Object subscriber) 方法来完成的。和订阅的原理类似,即是通过操作typesBySubscriber 变量的内容来完成的,不同的是订阅过程是往集合类里面添加数据,而取消注册正好相反,是从集合中移除数据。代码如下:
|
|
从上面的源码中可以看出,取消订阅的过程是和订阅的正好相反的,订阅的过程是把订阅信息从EventBus 的subscriptionsByEventType 和 subscribedEvents 两个成员中移除。
1.3 发布事件过程
事件的发布过程,是通过 EventBus.post(Object event) 方法来发布一个事件,post方法的源码如下:
首先,我们能够看到,事件的发布,是由一个队列控制着的,保证了事件的发生顺序是按照post 方法的调用顺序进行发布的。EventBus 中的currentPostingThreadState 是一个ThreadLocal对象,里面包含有一个 PostingThreadState 类型的对象,这个PostingThreadState 是EventBus 的静态内部类,代表着当前线程正在发布事件的状态,内部仅仅定义了一些成员,没有逻辑在内部,可以理解为一个数据的集合:
每一个被post 的事件,都会被放在当前线程的PostingThreadState 对象的 eventQueue 里面,这个 eventQueue 是一个列表(当作队列使用),在post 方法里面循环遍历 eventQueue,依次调用postSingleEvent方法,它的源码如下:
在postSingleEvent 方法的内部,会处理 eventInheritance 参数的逻辑,分别针对 eventInheritance设为true 和false两种情况进行了不同的处理,但是相同的是,最终都会调用到 postSingleEventForEventType 方法,它的逻辑如下
接下来,是到了 postToSubscription 方法中:
从上面的分析,可以看到发布事件的调用顺序是:
调用的步骤是:
- 调用 post() 方法开始发布事件;
- 把需要发布的事件,放在当前线程的发布队列里面;
- 循环把发布队列中的事件挨个进行处理;
- 处理 eventInheritance 参数的逻辑;
- 按照需要发布事件的线程类型(ThreadMode),来确定是直接调用invokeSubscriber方法来发布,还是先加到目标线程的队列中;
- 最终,调用 invokeSubscriber 方法,执行订阅方法(Method) 的invoke方法,通过反射来执行订阅方法,完成事件的发布;
以上,就是普通事件的发布过程。
1.4 发布粘性事件(Sticky Event)
粘性事件,是相对于普通事件来说的,对于普通的事件来说:先订阅、再发布事件是一个标准的步骤,也就是说,如果想要能接收到一个事件的话,那前提是得先订阅了这个事件,如果说在事件发布的时候,还没订阅这个事件,那么这个订阅者是收不到的这个事件的。
然而,在某些特殊的场景中,是能够希望订阅者的订阅过程哪怕是晚一点也能够收到事件的,能够实现的是,只要一个订阅者订阅了事件,那哪怕是在订阅前发生的这个事件,这个订阅者也照样能收得到。对于这样的特殊的事件,被称为粘性事件(Sticky Event)。粘性这个概念,指的就是这样的场景,这个概念在Android系统中也有,就是粘性广播,和这里的粘性事件类似的含义。
粘性事件的发布流程,大体上和普通事件的一样,是通过postSticky 方法实现的:
从源码中可以看出,Sticky Event 的发布,其实就是先把Sticky Event加入到stickyEvents集合里面,然后又调用了 post 方法。也就是说比普通的事件的发布多了一个加入到 stickyEvents 的过程。
这个 stickyEvents 是EventBus 的一个成员,是一个Map类型的集合对象,这个对象在 1.3 发布事件过程 里面已经提到过,在订阅的时候,当订阅的事件是Sticky 的时候,会check当前的 stickyEvents 里面是否存在该类型的Event,有的话,就直接调用到 postToSubscription 执行事件的发布了。
由此就可以明白,所谓的Sticky Event,其实就是在postSticky之后,把事件在 stickyEvents 成员中缓存起来,当后续有订阅者来订阅该类型的Event 的时候,则直接从 stickyEvents 缓存中读取事件并直接发布事件。这样,就做到了“先发布、后订阅”也能让订阅者收到粘性事件了。
1.5 线程调度
EventBus中的事件发布,支持多线程的调度,多线程的调度主要是用在事件的发布过程中的postToSubscription方法中:
从源码中可以看出来,线程的调度,都是通过Poster 接口的enqueue 方法来实现的。Poster接口代表一个和线程关联的事件队列,包含一个像线程事件队列的接口enqueue() 方法。
Poster 接口的定义很简答,只有一个装载事件到队列的方法:
Poster有多个子类的实现,分别对应了不同的线程处理模式:
- AsyncPoster: 对应于
ThreadMode.ASYNC模式,该模式总是把事件在一个新的线程中执行。内部是使用了EventBus 的 executorService 成员(线程池对象)来执行的; - BackgroundPoster: 对应于
ThreadMode.BACKGROUND模式,该模式会把事件发布到后台线程中执行。内部也是使用了EventBus 的 executorService 成员(线程池对象)来执行的; - HandlerPoster: 对应于
ThreadMode.MAIN模式,该模式是把事件发布到Android的主线程中执行。HandlerPoster 是继承了Android 的Handler 类。
在3个Poster 的子类中,AsynPoster 和BackgroundPoster的原理是类似的,都是通过EventBus的线程池对象来执行的。以BackgroundPoster 为例,它的 enqueue 方法如下:
而HandlerPoster类,它的构造,是使用了一个外部的 Looper对象,关于Looper机制,是Android平台上面的Event-Loop 机制的实现,这里不再赘述(这个外部的Looper对象,针对Android平台上来说,默认就是Android的主线程的Looper):
调用上面这个构造方法的地方,是在 MainThreadSupport.createPoster() 里面,而里面的Looper参数,则来自EventBus 的mainThreadSupport成员:
HandlerPoster 也实现了 Poster 的enqueue 方法:
从上面的源码中,能看出来对于ThreadMode.MAIN 模式的线程调度的实现,就是通过主线程的Looper关联一个Handler来实现消息的发布的。
以上,就是EventBus 的主要的源码的分析。