EventBus是适用于在 Android 和 Java 中订阅-发布形式的事件总线。
事件总线是对发布-订阅模式的一种实现、是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖。
特点
- 简化组件间的通信,事件的发送者和接收者之间完全解耦。
- 支持交付线程(线程切换)、订阅优先级、粘性功能。
- 快速、轻量,相对简单的代码。
Subscriber 注解
在使用 EventBus 时,我们通过其提供的register
方法来注册成为订阅者。而@Subscriber
用于注解订阅者的public
方法,被注解的方法参数代表着订阅的事件。
下面是注解的声明:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
它描述了事件应当怎样被订阅,各字段含义为:
- 线程模式——订阅方法根据发布事件所在的线程应当采取怎样的线程执行策略。
- 是否粘性——订阅者一旦订阅后是否接收之前所发布的该类型的粘性事件。
- 优先级别——同一事件发布到订阅者的次序将依据优先级别设置的大小。
其中线程模式ThreadMode
是个枚举,各元素及含义为:
元素 | 含义 |
POSTING | 订阅方法会在与事件发布所在同一线程中调用,这个模式下完全避免了线程切换所带来的开销。 |
MAIN | 订阅方法会在 |
MAIN_ORDERED | 事件总是以放入队列等待交付的形式来处理。 |
BACKGROUND | 与 MAIN 模式相反,订阅方法将被在后台线程中调用,假如不是在主线程发布事件,那就立马调用订阅方法,否则加入队列交付到后台线程按顺序处理。 |
ASYNC | 与 POSTING 相反,订阅方法会在单独的线程中调用,确保发布事件的线程和订阅方法的调用线程二者是独立的。 |
注册订阅者
通常是如下的写法:
EventBus.getDefault().register(this);
getDefault
便捷地提供了一个单例,register
方法分为两个过程,首先根据传入的订阅者找出它包含的订阅方法(Method实例+Subscriber注解信息),然后去遍历所有订阅方法进行订阅操作。
public void register(Object subscriber) {
//……
Class<?> subscriberClass = subscriber.getClass();
//过程一 获取订阅者包含的订阅方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
//过程二 关联记录订阅者和订阅方法
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
findSubscriberMethods
用于获取订阅者所要订阅的方法,内部会优先从缓存中查找便于提高效率,方法声明如下:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
METHOD_CACHE
是一个ConcurrentHashMap
负责缓存之前已查找过的订阅者的方法。
ignoreGeneratedIndex
标志位可在EventBus创建时设置,默认为false,索引信息查找(findUsingInfo
)和查找反射注解(findUsingReflection
)这两种方式的区别在于使用索引(APT 技术)可以提前获取到订阅者的订阅方法信息,避免后者运行时使用反射查找带来的开销以及可能出现的NoClassDefFoundError
异常。
在findUsingInfo
的内部实现中对于没有找到任何订阅方法的情况下会去调用findUsingReflection
的方式进行查找,那么就直接看findUsingInfo
:
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
订阅者可能还含有继承的关系,FindState
用来暂存订阅方法,当前类的订阅方法添加完毕后移向父类继续添加;由于类继承关系所以可能还涉及到订阅方法的重写,FindState
包含的checkAdd
主要用来避免对父类中的订阅方法重复添加。
prepareFindState
从数组池中返回空的FindState
实例,初始化好订阅者类型信息后开启循环获取SubscriberInfo
,而SubscriberInfo
最开始的获取就包含在索引信息中,匹配到的话就进行订阅方法添加操作,否则转而用反射形式,然后移向父类继续查找。遍历完成后从FindState
中获取订阅方法集合并且将它回收返回数组池中(Handler
中的Message
的回收)。
接着就是findUsingReflectionInSingleClass
了,在findUsingReflection中主要这个也是靠的这个方法。大致的流程就是通过反射获取订阅者的Method
数组,遍历匹配带有Subscribe
注解的方法然后构建出SubscriberMethod
添加进FindState
。
订阅方法SubscriberMethod
获取到了之后那么接着执行register
步骤二的subscribe
方法:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
//第一个map 存放了每个类型事件包含的全部订阅者
//当事件发布时用于后续检索出事件的订阅者们
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//不允许订阅者所订阅的事件当中具有相同类型的事件
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
//根据优先级放到list中合适的位置
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
//第二个map 存放了每个订阅者包含的所有订阅事件
//用于①判断订阅者是否已注册上。②取消注册时辅助完成从一个map的清理工作
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//省略粘性事件处理
}
EvnetBus
需要管理好所有订阅者和所有的事件,它通过三个Map保存管理。在这个for循环的subscribe
方法中,往前两个Map中填充数据,然后就是尝试往这个新的订阅者发布粘性事件,省略的代码将在后面粘性事件中。
发布事件
发布事件通过post方法
EventBus.getDefault().post(event)
EventBus 会通过ThreadLocal
为每个发送消息的线程绑定一个 PostingThreadState
对象,用于为每个线程维护一个消息队列及其它辅助参数。
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
//将事件添加到队列
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
//不断地从队列中获取事件发布处理
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
postingState
表明当前线程的事件发布状态,在发送事件开始前先将事件放入postingState
的一个队列中,然后不断从队列中取出事件去处理,确保事件进行有序地发布。
接下来就该看看这个postSingleEvent
是怎么个实现的了。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
//……
}
首先就是判断eventInheritance
这个变量在创建EventBus
时是否置为true(默认false),开启的话很明显假如现有的订阅者所订阅的事件类型是该事件所包含的任一接口或者父类,那么EventBus也会为其发送事件。
看看postSingleEventForEventType
,从注册时的subscriptionsByEventType
来取出该事件的订阅者,然后就是通过订阅时的threadMode反射执行方法。
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted;
try {
//根据线程模式去执行这个事件的订阅者的订阅方法
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
postToSubscription
方法中有三个Poster用于根据线程策略完成事件最后的发布处理
- HandlerPoster: 继承了Handler,逻辑大致是接收一个任务后,放入PendingPost任务队列中。如果此时处于空闲状态的话,发送一个空的Message,让handlMessage得到执行,其内部不断从任务队列取出任务进行处理,期间后续所有新的任务都会被放在队列中等待执行。handlMessage中使用到
maxMillisInsideHandleMessage
这个参数来控制整个方法的最长执行时间。 - BackgroundPoster:队列循环和同步锁的方式在EventBus的线程池中执行任务,期间所有新增的任务都会被post到队列中,直至队列任务全部执行完毕后退出。
- AsyncPoster:每次任务来临时都会发布到EvenBus的线程池中执行。
粘性事件
从之前的分析来看,要想让订阅者能接收到后续的事件,register操作需要在post前执行,这样订阅者相应的订阅的方法才有机会得到执行。而粘性事件则不需要考虑这样的先后顺序,它允许我们可以先发送事件后续再去注册订阅者。
public void postSticky(Object event) {
synchronized (stickyEvents) {
//第三个map
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
相比普通的post多了一个往stickyEventsMap
添加数据的操作。接着再看先前订阅过程二里省略的粘性部分代码:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//……
if (subscriberMethod.sticky) {
if (eventInheritance) {
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
//根据线程模式去执行这个事件的订阅者的订阅方法
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}
总结一下就是,发送粘性事件时先把该事件存入到stickyEvents
中,然后在等订阅者注册进行Map添加数据时,如果它的订阅方法是粘性的(@Subscribe(stick=true)
),那么就从stickyEvents
中取出相应的事件发布执行。
它的取消方法removeStickyEvent相反则就是从stickyEvent
中移除对应事件。
取消注册
取消注册也很简单,从之前的的两个Map中移除数据。
/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
就是从typesBySubscriber
找出该订阅者的所有订阅事件遍历subscriptionsByEventType
这个Map,找出与之相同的订阅者将其active属性置为false,这样确保该订阅者后续还未执行的事件不会得到执行,最后从集合中移除。
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
通过上面也能看到就是执行完后的typesBySubscriber
和subscriptionsByEventType
里虽然都没有了订阅者的相关内容,但是在一开始regiter过程中出现的 SubscriberMethodFinder
里的 METHOD_CACHE
依然是缓存着该订阅者的SubscriberMethod,便于后续订阅者再次订阅时可以直接从缓存中返回。
结语
EventBus的实现原理不算很复杂,有些内容与Handler的设计比较相像易于理解。它的功能如此稳定,以至于从它的上次版本发布到现在早已过去两年半。 iii