EventBus
简介
EventBus 是一个用于 Java 和 Android 的中央发布/订阅事件系统。事件被发布 post(Object) 到总线,总线将其传递给具有事件类型匹配处理程序方法的订阅者。要接收事件,订阅者必须使用 register(Object) 将自己注册到总线。注册后,订阅者会收到事件,直到调用 unregister(Object) 。事件处理方法必须由 Subscribe 注释,必须是公共的,不返回任何内容(void),并且只有一个参数(事件)
优点
- 简化组件之间的通信,解耦发布者和订阅者
- 体积小,使用简单
- 具有 delivery thread、订阅者优先级等高级属性
- 基于 APT 技术避免运行时反射,提高性能(本文基于运行时反射的使用方式分析)
基础概念
Event
事件。EventBus 会根据事件类型进行全局的发送
Subscriber
事件订阅者。通过 register(Object) 将自身注册到总线
Publisher
事件发布者。通过 post(Object) 发布事件到总线
线程模式(ThreadMode)
每个订阅者方法都有一个线程模式,它决定了 EventBus 将在哪个线程中调用该方法。 EventBus 独立于发布线程处理线程。
public enum ThreadMode {
/**
* 默认模式。订阅者将在发布事件的同一线程中直接调用。事件传递意味着最少的开销,因为它完全避免了线程切换。因此,对于已知可以在很短的时间内完成而不需要主线程的简单任务,这是推荐的模式。使用此模式的事件处理程序必须快速返回以避免阻塞可能是主线程的发布线程
*/
POSTING,
/**
* 在 Android 上,订阅者将在 Android 的主线程(UI 线程)中被调用。如果发布线程是主线程,订阅者方法将被直接调用,阻塞发布线程。否则,事件将排队等待传递(非阻塞)。使用这种模式的订阅者必须快速返回以避免阻塞主线程。如果不在 Android 上,则行为与POSTING相同
*/
MAIN,
/**
* 在 Android 上,订阅者将在 Android 的主线程(UI 线程)中被调用。与MAIN不同,事件将始终排队等待交付。这确保了 post 调用是非阻塞的。如果不在 Android 上,则行为与POSTING相同。
*/
MAIN_ORDERED,
/**
* 在 Android 上,订阅者将在后台线程中调用。如果发布线程不是主线程,订阅者方法将直接在发布线程中调用。如果发布线程是主线程,EventBus 使用单个后台线程,它将按顺序传递其所有事件。使用此模式的订阅者应尽量快速返回以避免阻塞后台线程。如果不在 Android 上,则始终使用后台线程。
*/
BACKGROUND,
/**
* 订阅者将在单独的线程中调用。这始终独立于发布线程和主线程。使用此模式发布事件永远不会等待订阅者方法。如果订阅者方法的执行可能需要一些时间,例如网络访问,则订阅者方法应使用此模式。避免同时触发大量长时间运行的异步订阅者方法,以限制并发线程数。 EventBus 使用线程池从已完成的异步订阅者通知中有效地重用线程。
*/
ASYNC
}
源码分析
创建 EventBus
- 通过 EventBug.getDefault() 获取一个进程内默认的全局 eventBus
- 通过 EventBus.builder() 设计模式,构造自定义的全局 eventBus
构造函数
EventBus(EventBusBuilder builder) {
//key - 事件类型 value - 订阅该事件的订阅者集合
subscriptionsByEventType = new HashMap<>();
//key - 订阅者 value - 该订阅者订阅的事件集合
typesBySubscriber = new HashMap<>();
//key - 粘性事件 class 对象 value - 事件对象
stickyEvents = new ConcurrentHashMap<>();
//用来创建 UI 线程事件发送者
mainThreadSupport = builder.getMainThreadSupport();
//UI 线程事件发送者
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
//background 事件发送者
backgroundPoster = new BackgroundPoster(this);
//async 事件发送者
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
//订阅者订阅事件查找对象
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
//线程池
executorService = builder.executorService;
}
注册过程
//消息类型-订阅该事件的订阅者集合(订阅者-订阅方法)
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//订阅者-该订阅者订阅的事件合集
private final Map<Object, List<Class<?>>> typesBySubscriber;
基本思路是通过运行时反射,寻找订阅者中的订阅方法,根据事件类型添加到集合中。然后建立订阅者和订阅方法的关系。
public void register(Object subscriber) {
//判断当前是否是在 Android 系统上使用 eventbus
if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {
// Crash if the user (developer) has not imported the Android compatibility library.
throw new RuntimeException("It looks like you are using EventBus on Android, " +
"make sure to add the \"eventbus\" Android library to your dependencies.");
}
Class<?> subscriberClass = subscriber.getClass();
//找到订阅者订阅的方法集合
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
//建立订阅关系
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
subscriberMethodFinder 用来查找订阅着中的订阅方法。我们来看 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;
}
}
ignoreGeneratedIndex 默认 false,所以会执行 findUsingInfo 方法来获取订阅者中的订阅方法。findUsingReflection 方法使用反射来获取回调方法的,findUsingInfo 方法在没有从注解处理器生成的 Index 文件中找到回调方法时,也会使用反射来获取回调方法。所以我们直接来看 findUsingReflection 方法的具体实现。
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
//从缓存池获取一个 FindState 对象
FindState findState = prepareFindState();
//初始化 findState 对象
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
首先通过 prepareFindState 方法尝试从缓存池中取出一个 FindState 对象,如果池子中没有可用的对象就会创建一个新的对象,接着对 FindState 对象初始化。然后通过一个 while 循环,遍历当前类及其父类,通过反射寻找订阅方法。我们接着来看 findUsingReflectionInSingleClass 方法。
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
//获取该类的所有方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
//获取该类及父类的 public 方法,同时跳过父类
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
//判断方法是 PUBLIC ,并且不是 ABSTRACT | STATIC | SYNTHETIC(编译器合成的方法)
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
//方法的参数只有一个
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
//方法有 Subscribe 注解
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
//方法校验。如果子类和父类有相同的方法签名,子类覆盖父类,父类不做处理。
if (findState.checkAdd(method, eventType)) {
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, subscribeAnnotation.threadMode(),
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
//如果开启严格方法验证并且方法的参数不止一个,抛出异常
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
//如果开启严格方法验证并且方法有 Subscribe 注解,抛出异常
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
可以看出,订阅类的方法需要满足以下条件才能完成订阅。
- 方法必须是 PUBLIC 修饰,并且不能是 ABSTRACT | STATIC | SYNTHETIC
- 方法的参数只能有一个
- 方法要有 Subscribe 注解
我们接着来看订阅者和订阅方法如何建立关系。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
//封装成 Subscription 对象
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
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++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// typesBySubscriber :以订阅者为 key,订阅事件类型为 value 进行保存
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//对粘性事件处理
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
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);
}
}
}
首先将订阅者和订阅方法封装成 Subscription 对象,然后存储到相应的集合中。
- subscriptionsByEventType :key 是事件类型,value 是 Subscription 对象集合。
- typesBySubscriber :key 是 当前的订阅着,value 是 当前订阅着中订阅的事件类型集合
接着如果该订阅方法需要处理粘性事件,则触发处理最新的粘性事件。
发布事件
Ev entBus 通过 post 和 postSticky 两种方式发送事件。我们先来看通过 postSticky 发送粘性事件。
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
发送粘性事件的过程很简单。一是将粘性事件保存到集合中,当订阅者注册时,相应粘性事件;二是调用 post 方法进行事件的分发。
//线程局部变量
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new 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;
}
}
}
post 的事件会加入一个线程单例的事件队列,通过 while 循环不断取出队列的头部事件使用 postSingleEvent 进行分发。postSingleEvent 的代码就不贴了,主要看内部的 postSingleEventForEventType 的实现逻辑。
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 1
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
// 2
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
// 3
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
在 1 处,获取该事件类型的所有订阅方法,在 2 处进行遍历,最终通过 postToSubscription 进行事件的分发,具体的分发逻辑如下。
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
根据订阅方法的 ThreadMode,有不同的分发方式。
| ThreadMode | 分发方式 |
|---|---|
| POSTING | 在发布线程通过反射直接调用 |
| MAIN | 如果发布线程是主线程,订阅者方法将被直接调用,阻塞发布线程。否则,事件将排队等待传递(非阻塞) |
| MAIN_ORDERED | 在 Android 上,订阅者将在 Android 的主线程(UI 线程)中被调用。与MAIN不同,事件将始终排队等待交付。这确保了 post 调用是非阻塞的 |
| BACKGROUND | 如果发布线程不是主线程,订阅者方法将直接在发布线程中调用。如果发布线程是主线程,EventBus 使用单个后台线程处理 |
| ASYNC | 无论发布线程是主线程还是后台线程,订阅者都将在独立线程调用 |
无论最终订阅者在哪个线程处理,最终都是通过 invokeSubscriber 通过反射回调订阅者的订阅方法。
注销过程
注销过程原理比较简单,就是通过事件类型 eventType 将注册时保存到 subscriptionsByEventType 、typesBySubscriber 两个集合中的元素进行删除,代码就不贴了,读者可自行查看源码。
总结
到这里 EventBus 的源码算是分析了绝大部分。后续会补上如何通过 APT 避免反射。如果哪里有写的不对的地方,欢迎大家指正。