检查更新2020年3月5日 14:17:30
Event bus for Android and Java that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality
简单理解一下就是 Event bus 给 Android 及 Java (当然主要是 Android)的 Activity,Fragment,Threads,Services 之间提供一个简单的通信方式,从而能让我们用质量高且少的代码来完成我们的功能。
传统的事件传递方式包括:Handler、BroadCastReceiver、Interface 回调,相比之下 EventBus 的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦。
一、使用
1.1gradle中引入
api 'org.greenrobot:eventbus:3.0.0'
1.2 定义消息事件
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
1.3 准备订阅者
订阅者实现事件处理方法(也称为“订阅者方法”),这些方法将在发布事件时调用。并使用 @Subscribe 注释定义。
// 此方法将在 UI 线程执行
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
// 此方法将在发送事件的线程执行
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
doSomethingWith(event);
}
订阅者需要在 EventBus 中注册和注销。只有当订阅着注册后,才会接受事件。在 Android 中,activities 和 fragments 通常在他们生命周期执行。例如 onStart/onStop:
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
1.4发送事件
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
粘性事件
粘性事件用于有一些场景,比如在已经发送事件后,才会注册订阅者。这时候,就不需要用户自己做消息缓存,只需要使用粘性事件,在注册订阅者时,会自动检查发送事件。
// 发送粘性事件
EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
// 移除粘性事件
MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
if(stickyEvent != null) {
EventBus.getDefault().removeStickyEvent(stickyEvent);
// Now do something with it
}
普通事件和粘性事件区别:
如果发布的是普通事件,当前如果没有Subscriber,则后续注册的Subscriber也不会收到该事件。
如果发布的是粘性事件,当前如果没有Subscriber,内部会暂存该事件,当注册Subscriber时,该Subscriber会立刻收到该事件。
二、EventBus 设计思路、源码分析
注册订阅者时,通过反射,把注解了 Subscribe 的方法保存到自己相对的 Map 数据项中;
发送消息时,从 Map 数据项中获取对应类型的订阅者,通过反射方法执行;
注销时,就会把订阅者中缓存到 Map 数据项的数据删除。
2.1 构造EventBus
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
采用双重校验并加锁的单例模式生成EventBus实例。这个单例模式实现并不会有多线程的安全问题。因为对于 defaultInstance 的定义是 volatile 的。
static volatile EventBus defaultInstance;
EventBus主要成员变量:
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;
subscriptionsByEventType:以event(即事件类)为key,以订阅列表(Subscription)为value,事件发送之后,在这里寻找订阅者,而Subscription又是一个CopyOnWriteArrayList,这是一个线程安全的容器。
typesBySubscriber:以订阅者类为key,以event事件类为value,在进行register或unregister操作的时候,会操作这个map。
stickyEvents:保存的是粘性事件
Subscription是一个封装类,封装了订阅者、订阅方法这两个类。定义了两个成员变量,
final Object subscriber; // 订阅一个事件的对象
final SubscriberMethod subscriberMethod; // 订阅的具体信息(方法名/ThreadMode/isStrick/priority)
具体代码:
final class Subscription {
final Object subscriber; // 订阅者类
final SubscriberMethod subscriberMethod; // 订阅者类对于的方法信息
}
public class SubscriberMethod {
final Method method; // 订阅者方法
final ThreadMode threadMode; // 线程模式
final Class<?> eventType; // 订阅的事件类型
final int priority;
final boolean sticky;
/** Used for efficient comparison */
String methodString;
}
2.2 register
代码部分:
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
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<>();
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;
}
}
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
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);
}
}
}
这段代码主要执行
-
findSubscriberMethods 查找该类中所有注解了 Subscribe 的方法,并保存到 List中;
2.在subscribe 方法中,针对每个订阅方法处理;
3.把 SubscriberMethod 封装为 Subscription,并添加到 subscriptionsByEventType;
4.向 typesBySubscriber 添加订阅者方法信息;
5.检查是否该订阅者方法在粘性事件中有相同的事件类型,如果有则会触发方法通知。
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(...); } else { // 将获取到的订阅方法放置到缓存当中 METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }
findSubscriberMethods 方法通过反射,获取注解了 Subscribe 的方法,并保存到 SubscriberMethod 里。
静态常量 METHOD_CACHE,中缓存了 findSubscriberMethods 中注册过的类,有效的避免了重复注册过的类,再次通过反射去查找,提高性能
2.2 unregister
代码
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 {
...
}
}
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--;
}
}
}
}
从这个代码可以看出来,unregister就是把订阅者信息从两个map变量中删除。
subscriptionsByEventType key 为 Class 类型,保存有事件类型;value 为 ArrayList 类型(Subscription 是一个保存有订阅者信息的类,我们之后分析)一个事件可以被多个类订阅(但同一个类不能有多个方法同时订阅同一个事件类型)。
typesBySubscriber ,key 为 Class 类型,保存订阅者类;value 为 List 类型,保存每个订阅者订阅的事件类型。
2.3 post
事件发布,
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;
...
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
..
}
}
private void postSingleEvent(Object event, PostingThreadState postingState) {
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);
}
...
}
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 = false;
postToSubscription(subscription, event, postingState.isMainThread);
...
}
return true;
}
return false;
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
//...
}
void invokeSubscriber(Subscription subscription, Object event) {
}
放到事件队列里,并运行 postSingleEvent 方法执行事件;
postSingleEventForEventType 方法中通过 eventClass 获取所有订阅的方法;
postToSubscription 方法中包含一些线程的切换,代码见下,如果是主线程或者其他线程,会放到线程对应的队列中;
invokeSubscriber 方法中通过反射执行方法。
2.4 指定线程
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);
}
}
说明:
- POSTING(默认):事件在哪个线程发布,就在哪个线程消费,因此要特别注意不要在UI线程进行耗时的操作,否则会ANR。
- MAIN:事件的消费会在UI线程。因此,不宜进行耗时操作,以免引起ANR。
- BACKGROUND:如果事件在UI线程产生,那么事件的消费会在单独的子线程中进行。否则,在同一个线程中消费。
- ASYNC:不管是否在UI线程产生事件,都会在单独的子线程中消费事件。
根据当前的线程状态和订阅方法指定的threadMode信息来决定合适触发方法。这里的invokeSubscriber会在当前线程中立即调用反射来触发指定的观察者的订阅方法。否则会根据具体的情况将事件加入到不同的队列中进行处理。这里的mainThreadPoster最终继承自Handler,当调用它的enqueue方法的时候,它会发送一个事件并在它自身的handleMessage方法中从队列中取值并进行处理,从而达到在主线程中分发事件的目的。这里的backgroundPoster实现了Runnable接口,它会在调用enqueue方法的时候,拿到EventBus的ExecutorService实例,并使用它来执行自己。在它的run方法中会从队列中不断取值来进行执行。
2.5 粘性事件
postSticky 函数做了两个事情:
把事件缓存到 stickyEvents 中,再有新的订阅者注册的时候可以检查通知事件;
像 post 普通的事件一样,执行 post 方法
三、问答部分
- 在EventBus中,使用
@Subscribe
注解的时候指定的ThreadMode
是如何实现在不同线程间传递数据的?
要求主线程中的事件通过Handler
来实现在主线程中执行,非主线程的方法会使用EventBus内部的ExecutorService
来执行。实际在触发方法的时候会根据当前线程的状态和订阅方法的ThreadMode
指定的线程状态来决定何时触发方法。非主线程的逻辑会在post
的时候加入到一个队列中被随后执行。
- 使用注解和反射的时候的效率问题,是否会像Guava的EventBus一样有缓存优化?
内部使用了缓存,确切来说就是维护了一些映射的关系。但是它的缓存没有像Guava一样使用软引用之类方式进行优化,即一直是强引用类型的。
- 黏性事件是否是通过内部维护了之前发布的数据来实现的,是否使用了缓存?
黏性事件会通过EventBus内部维护的一个事件类型-黏性事件
的哈希表存储,当注册一个观察者的时候,如果发现了它内部有黏性事件监听,会执行post
类似的逻辑将事件立即发送给该观察者。
参考:1.https://www.cnblogs.com/all88/archive/2016/03/30/5338412.html