前言
EventBus 使用发布/订阅者模式,能够很好的进行模块之间的通信、解耦。以前一直停留在会用的层次,为了探究底层实现阅读了其源码实现,这也是继 Volley 以外我第二个阅读的框架源码。
概述
EventBus 仓库地址为:https://github.com/greenrobot/EventBus 。官方给出以下一张图来帮助理解。
Publisher 使用 post 方法发送 Event ,Subscribe 在 onEvent 方法中接收 Event。据官方介绍 EventBus 有如下优点:
- 简化组件间的交流。
- 分离事件发送至和接收者。
- 更好的处理 Activities、Fragments、Background threads。
- 避免复杂且易出错的依赖关系和生命周期问题。
- 使你的代码更简单。
- 速度快。
- 库较小。
- 使用该库的 APP 多。
- 拥有一些高级功能比如指定分发线程,设置订阅者优先级等。
基本用法
这里以 EventBusSecondActivity 通知 EventBusFirstActivity 为例,代码如下:
class MessageEvent() // 1 |
- 定义事件类: MessageEvent ,如果需要额外信息可以添加字段。
- 准备订阅者:声明以及注解订阅方法 onReceiveEvent ,可选 threadMode、sticky 、priority 。
- 注册订阅者:调用
EventBus.getDefault().register(this)
。 - 发送事件:调用
EventBus.getDefault().post(MessageEvent())
。
经过以上四步,EventBusFirstActivity.onReceiveEvent 就会被执行。下面来分析下源码实现。
Subscribe 注解
首先看看 Subscribe 注解。
|
一共有以下三个属性:
- threadMode 表示注解的方法需要在哪个线程执行。可选值为(当前线程为执行 post 方法的线程):
- POSTING,直接在当前线程调用。
- MAIN,如果当前线程是主线程那么在当前直接调用,如果不是那么切换线程到主线程调用,因此调用线程一定是主线程。
- MAIN_ORDERD,对比 MAIN ,其不判断当前是哪个线程,全部切换到主线程调用,因此调用线程一定是主线程。
- BACKGROUND,如果当前线程是主线程那么切换到子线程中调用,并且多个事件是串行处理的,如果不是那么直接在当前线程调用,因此调用线程一定是子线程。
- ASYNC,对比 BACKGROUND ,其不判断当前是哪个线程,全部切换到子线程中调用,并且多个事件是并行处理的,因此调用线程一定是子线程。
- sticky 表示是否接收在注册前已经发送的粘性事件。
- priority 表示订阅方法调用的优先级,只对于相同的 threadMode 生效。
EventBus.register
接着看看 register 方法。
public EventBus() { |
- 双重校验锁单例模式,但是 EventBus 的构造器不是私有的,并且还可以通过 EventBusBuilder.build 创建实例,这里只考虑使用 EventBus.getDefault 的情况。
- 调用 SubscriberMethodFinder.findSubscriberMethods 查询类内所有注解了 @Subscribe 的方法。
- 对每个查询到的方法执行 EventBus.subscribe 方法。
SubscriberMethodFinder.findSubscriberMethods
看看 EventBus 是如何寻找当前类中所有含有 Subscribe 注解的方法的。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { |
- 如果
METHOD_CACHE
(ConcurrentHashMap) 里面已经存在了,那么使用缓存。 - 默认
ignoreGeneratedIndex
(是否忽略 APT 生成的 Index )为 false,因此会走到findUsingInfo(subscriberClass)
。 - 如果在类中没找到注解了 @Subscribe 的方法那么抛出异常。
- 将找到的方法放入
METHOD_CACHE
进行缓存。
SubscriberMethodFinder.findUsingInfo
寻找到当前订阅者以及父类所有订阅方法
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { |
- 从缓存中取出或者创建一个实例。
- 将
findState
中的subscribeClass
、clazz
字段都置为subscribeClass
,为寻找方法做准备。 - 根据
findState
找出订阅信息,如果不使用 APT,这里一开始会返回 null。 - 调用
findUsingReflectionInSingleClass
找出findState.clazz
类中所有符合的方法。 - 调用
findState.moveToSuperclass
将findState.class
变为其父类,寻找父类中所有符合的方法。 - 调用
getMethodsAndRelease
获取所有订阅的方法,并回收 FindState。
SubscriberMethodFinder.prepareFindState
prepareFindState 用于从缓存中取出或者创建一个实例
private FindState prepareFindState() { |
如果 FIND_STATE_POOL
有 FindState 实例,那么取出一个,否则新建一个实例。
SubscriberMethodFinder.initForSubscriber
initForSubscriber 用于初始化 FindState
void initForSubscriber(Class<?> subscriberClass) { |
将 subscribeClass
、clazz
字段都置为 subscribeClass
,为寻找方法做准备,clazz 会不断改变,从当前类到其父类。
SubscriberMethodFinder.findUsingReflectionInSingleClass
findUsingReflectionInSingleClass 通过反射获取一个类中所有订阅的方法
private void findUsingReflectionInSingleClass(FindState findState) { |
getDeclaredMethod
速度比getMethod
快,前者获取的是当前类的所有方法,后者获取的是当前类及其父类的所有公有方法,如果使用了getMethod
那么就跳过从父类中寻找方法。method
必须满足,可见性为 public、非静态、非抽象、参数一个、 @Subscribe 注解,否则跳过。- 执行
findState.checkAdd
存储订阅方法,这个方法很难理解但是很重要,决定了该方法要不要。 - 将符合条件的方法封装成 SubscriberMethod 实例,加入到 FindState 的
subscriberMethods
中。 - 各种开启严格模式,抛出异常的情况。
FindState.checkAdd
代码如下:
boolean checkAdd(Method method, Class<?> eventType) { |
由于方法相对比较难以理解,其实就是检查该方法是否需要添加,这里以类 A 存在两个接收 Message 类实例的方法 a1、a2 为例。
- a1 方法进入,Message 事件还没存在于
anyMethodByEventType
,那么existing
为 null ,方法直接返回 true。 - a2 方法进入,Message 事件已经存在于
anyMethodByEventType
,那么existing
不为空,执行checkAddWithMethodSignature
将 a1 方法传入,a1 的 methodKey 不存在于subscriberClassByMethodKey
,那么methodClassOld
为空,直接返回 true ,然后直接把当前 FindState 实例放入anyMethodByEventType
,原先的 a1 被替换成 FindState 实例,接着执行第二次checkAddWithMethodSignature
将 a2 方法传入,a2 的 methodKey 不存在于subscriberClassByMethodKey
,那么methodClassOld
为空,直接返回 true , 来看看现在内存情况,anyMethodByEventType
存放 Message - FindState 直接的映射,subscriberClassByMethodKey
存放 a1 - A、b1 - B 。 - 经过上述两步,应该基本明白了,如果订阅类一个事件就对应一个方法,那么
anyMethodByEventType
就已经够用了,内部存放 Message - Method 映射,如果一个事件对应多个方法,那么anyMethodByEventType
不够用,内部存放 Message - FindState 映射。
FindState.moveToSuperClass
将当前反射的 class 改为父 class。
void moveToSuperclass() { |
如果不跳过查询父类,将 FindState 的 clazz
字段变成其父类的 Class 实例,如果类全名遇到 java. 、javax. 、android. 、androidx. 开头那么跳过。
SubscriberMethodFinder.getMethodsAndRelease
从 FindState 中获取该订阅者所有的订阅方法,并重置 FindState。
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) { |
思考:是否有必要这么设计 FindState ? 我认为可以使用 Map<Class, Set<String>> 替代 FindState 内部的两个 Map,如果遇到消息类型不一致那么允许添加,如果遇到消息类型一致那么判断方法签名是否存在 Set 中,如果已经存在了其实就表示是子类重写了父类的方法直接不允许添加就行。
回到 EventBus.register ,看看 subscribe
方法。
EventBus.subscribe
subscribe 用于整合所有订阅的方法,以及处理粘性事件。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { |
- 将订阅者对象与找到的方法封装成 Subscription 对象。
- 维护数据结构 Map<Class, CopyOnWriteArrayList<Subscription>> key 为事件的 Class 对象,value 为接收该事件的所有订阅。
- Subscription 类重写了 equals 方法,如果订阅者对象一样,并且内部的订阅方法也一样那么抛出异常。
- 按照优先级顺序插入到 CopyOnWriteArrayList 中。
- 维护数据结构 Map<Object, List<Class<?>>> key 为订阅者对象,value 为该类所有事件的 Class 列表。
- 处理粘性事件,稍后再解释。
注册流程已经完成,下面看下发送流程。
EventBus.post
post 用于发送事件。
public void post(Object event) { |
- 获取当前线程的 PostThreadState 实例。
- 执行
postSingleEvent
方法
EventBus.postSingleEvent
post 用于发送单个事件。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { |
- 默认情况下
eventInheritance
为 true ,该属性表示事件是否继承,比如发送 Message 事件,那么同时也会发送 Parcelable 事件,因为 Message 实现了 Parcelable 接口。 - 执行
postSingleEventForEventType
进行事件的发送。 - 如果找不到任何接收事件的方法,那么发送 NoSubscriberEvent 事件。
EventBus.postSingleEventForEventType
发送单个事件的消息
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { |
- 获取原先注册时收集为接收该事件的 subscriptions 。
- 执行
postToSubscription
进行事件的发送。
EventBus.postToSubscription
根据不同的 threadMode 进行不同的操作
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { |
- POSTING,直接在当前线程使用反射调用。
- MAIN,如果当前是主线程,那么直接调用,否则执行
mainThreadPoster.enqueue
。 - MAIN_ORDERED,由于默认情况
mainThreadPoster
不为空,于是执行mainThreadPoster.enqueue
。 - BACKGROUND,如果当前是主线程,那么执行
backgroundPoster.enqueue
,否则直接反射调用。 - ASYNC,执行
asyncPoster.enqueue
。
HandlerPoster.enqueue
封装 subscription 以及 event 成 PendingPost 并加入队列,然后发送消息在主线程执行调用。
public void enqueue(Subscription subscription, Object event) { |
- 从 PendingPost 池中取出或者创建一个 PendingPost 实例,并将其放入队列中(单链表实现)。
- 如果
handleMessage
的 while 循环没有执行,那么发送消息使其执行。 - 从队列中取出消息,如果取不到则使用同步再取一次,可能情况是其它线程添加进去了当前线程不可见,使用同步强制去主存读一次,取到后回收 PendingPost 实例,然后反射执行。
- 如果事件总执行时间超过
maxMillisInsideHandleMessage
那么重新发送事件,意图是防止卡顿,给 UI 刷新执行时间,不然一直卡在这循环了。
BackgroundPoster.enqueue
封装 subscription 以及 event 成 PendingPost 并加入队列,然后在线程池中调用,事件顺序执行。
public void enqueue(Subscription subscription, Object event) { |
- 从 PendingPost 池中取出或者创建一个 PendingPost 实例,并将其放入队列中(单链表实现)。
- 如果当前 run 方法还没被执行,那么将其放入线程池中执行,默认线程池为
Executors.newCachedThreadPool
由于无线程上界几乎可以立即执行。 - 从队列中取出消息,如果1秒内取不到则使用同步再取一次,可能情况是其它线程添加进去了当前线程不可见,使用同步强制去主存读一次,取到后回收 PendingPost 实例,然后反射执行,事件是顺序执行的,必须等到上个事件处理完毕后,下一个才能被处理。
AsyncPoster.enqueue
封装 subscription 以及 event 成 PendingPost 并加入队列,然后在线程池中调用。事件并发执行。
public void enqueue(Subscription subscription, Object event) { |
获取 PendingPost 对象,将其放入队列,然后将自身放入线程池执行 run 方法,在内部再取出 PendingPost 对象,反射执行方法调用,与 BackgroundPoster 不同的是,事件是并发执行的,只要有事件就立马执行,下一个不需要等上一个事件处理完毕。
至此,注册订阅以及发送事件流程已经完成了,下面来看看取消注册。
EventBus.unregister
该方法用于解除注册,内部会把所有当前订阅者已经订阅的事件移除。
public synchronized void unregister(Object subscriber) { |
首先从 typesBySubscriber
中取出订阅者类拥有的所有订阅类型,然后再根据订阅类型找到对应的 subscriptions
,并且遍历如果发现其订阅者是 subscriber
将其 activie
置为 false(防止订阅方法再被执行) 然后将其移除。就完成了注册和解注册,接着看看粘性事件。
EventBus.postSticky
该方法用于发送粘性事件
public void postSticky(Object event) { |
相对于非粘性事件,只是多了往 stickyEvents
里面添加事件类以及事件对象,想想也是,粘性事件相比非粘性只是在注册时可能会立即执行,那么回到 EventBus.subscribe 。
EventBus.subscribe
看看究竟是如何处理粘性事件的
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { |
- 首先如果注解属性 sticky 为 true 才会进入。
- 默认
eventInheritance
为 true 。 - 从
stickyEvents
中取出已缓存的粘性事件对象,如果订阅的事件是粘性事件或者是其父类,那么执行checkPostStickyEventToSubscription
。 - 如果
eventInheritance
为 false ,那么从stickyEvents
中取出已缓存的粘性事件对象,如果订阅的事件是粘性事件,那么执行checkPostStickyEventToSubscription
。
EventBus.checkPostStickyEventToSubscription
代码如下:
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) { |
又回到了 postToSubscription
根据 ThreadMode 在对应线程反射执行方法。
总结
EventBus 的核心是以下三个 Map:
- Map<Object, List<Class> 用于解除注册。
- Map<Class, CopyOnWriteArrayList<Subscription>> 用于分发事件。
- Map<Class, Object> 用于粘性事件。
基于反射的 EventBus 会对性能有一点影响,后续看一看其提供的 APT 插件。