EventBus使用及其原理解析
什么是EventBus?
- EventBus是一种用于Android的发布/订阅事件总线。它有很多优点:简化应用组件间的通信;解耦事件的发送者和接收者;避免复杂和容易出错的依赖和生命周期的问题;很快,专门为高性能优化过等等。
- EventBus采取的是订阅者/发布者的模式,发布者通过EventBus发布事件,订阅者通过EventBus订阅事件。当发布者发布事件时,订阅该事件的订阅者的事件处理方法将被调用。
EventBus的使用
刚才说过,EventBus采用的是订阅者/发布者模式,也就是说,我们要先建立一个事件,然后再想发送相关信息的组件创建发布者,然后在要接收信息的组件创建订阅者并且获取信息。大致分如下四步:
创建一个发送事件类
public class EventMessage {
String account;
public EventMessage(String account) {
this.account = account;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
}
在其中写出要传递的信息,在加入get/set方法即可。
定义订阅者方法
定义一个事件处理方法(也称为订阅者方法),当指定类型事件被发布时,此方法会被调用。EventBus使用@Subscribe注解来定义订阅者方法。方法名可以是任意合法方法名,参数类型为订阅事件的类型。
@Subscribe(threadMode = ThreadMode.POSTING,sticky = true)
public void showEventMessage(EventMessage message){
textView.setText(message.getAccount());
}
注册和注销订阅事件
@Override
protected void onStart(Bundle savedInstanceState) {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
protected void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
通常在onStart方法中注册订阅者,在onStop方法中注销订阅者。
发送消息
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
EventBus的源码导读
Subscribe注解的使用
在刚才的使用中我们得知,在每一个订阅者方法上面都要加上Subscribe注释并传入参数,那么我们看一下Subscribe注解的源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
// 线程的模式,默认为POSTING
ThreadMode threadMode() default ThreadMode.POSTING;
// 是否坚持粘性事件,默认不支持
boolean sticky() default false;
// 一个优先级标识,默认为0
int priority() default 0;
}
上面的threadMode是存放了一个线程模式,会根据这个线程模式,来处理订阅者方法被调用所在的线程:
public enum ThreadMode {
// 在当前的线程中直接处理。
POSTING,
// 在主线程中发送事件,则直接处理,但是要求处理量小;在子线程中,就通过Handler发送后,再由主线程处理
MAIN,
// 总是入队列进行等待,通过Handler发送事件后,再由主线程处理
MAIN_ORDERED,
// 在主线程中发送事件,入队列依次处理;在子线程中发送事件,则直接处理。
BACKGROUND,
// 总是入队列等待,通过线程池异步处理。
ASYNC
}
- ThreadMode.POSTING 订阅者方法将在发布事件所在的线程中被调用。这是 默认的线程模式。事件的传递是同步的,一旦发布事件,所有该模式的订阅者方法都将被调用。这种线程模式意味着最少的性能开销,因为它避免了线程的切换。因此,对于不要求是主线程并且耗时很短的简单任务推荐使用该模式。使用该模式的订阅者方法应该快速返回,以避免阻塞发布事件的线程,这可能是主线程。
- ThreadMode.MAIN订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。如果发布事件的线程是主线程,那么该模式的订阅者方法将被直接调用。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
- ThreadMode.MAIN_ORDERED 订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。事件将先进入队列然后才发送给订阅者,所以发布事件的调用将立即返回。这使得事件的处理保持严格的串行顺序。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
- ThreadMode.BACKGROUND 订阅者方法将在后台线程中被调用。如果发布事件的线程不是主线程,那么订阅者方法将直接在该线程中被调用。如果发布事件的线程是主线程,那么将使用一个单独的后台线程,该线程将按顺序发送所有的事件。使用该模式的订阅者方法应该快速返回,以避免阻塞后台线程。
- ThreadMode.ASYNC 订阅者方法将在一个单独的线程中被调用。因此,发布事件的调用将立即返回。如果订阅者方法的执行需要一些时间,例如网络访问,那么就应该使用该模式。避免触发大量的长时间运行的订阅者方法,以限制并发线程的数量。EventBus使用了一个线程池来有效地重用已经完成调用订阅者方法的线程。
说完了注解和线程模式,下面我们就针对EventBus的注册注销和发送事件处理事件的源码进行分析。
两个全局变量map
进行源码分析之前,我要先说EventBus的两个全局变量,typeBySubscriber和subscriptionsByEventType。
// Map构成的类,使用时Key对应Activity,value对应type
// 存储的是Activity下对应的所有事件类型
private final Map<Object, List<Class<?>>> typesBySubscriber;
// Map构成的类,使用时Key对应type,value对应订阅集合
// 存储的是对应的事件类型下的subscriptions
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
也就是说
- typeBySubscriber是一个以订阅者(activity范围)为key,以该订阅者所订阅的所有事件类型组成的集合为value
- subscriptionsByEventType是一个以订阅事件类型为key,以所有订阅该事件类型的订阅者的集合为value。
说完这两个全局变量,我们对源码会有更好的理解能力。
注册流程
EventBus.getDefault()
为什么我们调用EventBus.getDefault()可以完成不同activity的操作?比如post,register,unregister?
是因为EventBus.getDefault()这个方法调用的是DCL的单例模式:
static volatile EventBus defaultInstance;
public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}
众多activity只持有一个eventBus对象。
register(this)
我们调用的是EventBus.getDefault().register(this)来注册的,所以第一步一定是进入register的源码:
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
// 就是当前的Activity中寻找到带有@Subscribe注解的方法集合
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
// 线程同步方式完成订阅事件的绑定
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod); // 1 -->
}
}
}
这个方法的工作只有两步:
- 第一步:调用findSubscriberMethods(subscriberClass)方法,去获取当前activity中所有带有@Subscribe注解的方法集合,也就是获取当前订阅者(activity范围)所有的订阅方法即订阅事件类型。
- 第二步:调用subscribe(subscriber, subscriberMethod)方法,传入当前订阅者,和其包含的所有订阅方法。
接下来我们看是如何获取的订阅方法。
findSubscriberMethods()
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// 先是从缓存中取,有则直接返回,无则寻找
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
// 因为我们一般使用的都是getDefault()来进行一个注册,所以ignoreGeneratedIndex默认为false。去查看EventBusBuilder即可,会看到未赋值,也就是默认值的false
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;
}
}
这个方法的逻辑就是:先去缓存里面找,有的话直接获取返回,没有的话调用findUsingInfo(subscriberClass)方法进行继续查询,然后查询到将其缓存到内存中。
接下来又到了findUsingInfo方法。
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 {
// 一般进入这个方法,通过反射的方式判断方法使用是否满足一些基本条件
// 是否是public修饰,是否为一个参数,是否为静态,是否为抽象类
findUsingReflectionInSingleClass(findState);
}
// 用于跳过对一些Android SDK的查询操作
// 可以防止一些ClassNotFoundException的出现
findState.moveToSuperclass();
}
// 对遍历获得的方法进行重构
// 对findState进行回收
return getMethodsAndRelease(findState);
}
这个方法里面就是通过反射来获取所有的方法,并且判断方法是否满足一些基本条件。
至此,我们就拿到了当前订阅者包含的所有订阅方法。然后就到了register()方法中的最后一步:subscribe()
subscribe()方法
作为注册的最后一个方法,直接上代码
//传入参数,订阅者(activity范围)和订阅者所包含的所有订阅方法
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 找到对应订阅方法订阅的事件类型
Class<?> eventType = subscriberMethod.eventType;
// 根据subscriber和事件类型创建一个subscription对象
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// 通过事件类型,拿到subscription订阅事件集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
// 如果subscriptionsByEventType中并不存在对应的eventType,就创建并存储
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
......
}
// 将新的订阅事件subscription根据优先级放入订阅事件集合subscriptions中,也就是简介完成了在subscriptionsByEventType的添加
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;
}
}
// 以订阅者为key,获取它订阅的所有事件类型的集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
//向集合中加入当前新订阅的事件类型,即完成了在typesBySubscriber中的添加
subscribedEvents.add(eventType);
// 对粘性事件的一个具体操作
if (subscriberMethod.sticky) {
// 。。。。。。
}
}
这个方法中,我们主要是对之前说过的两个变量typesBySubscriber、subscriptionsByEventType进行操作,把订阅者和订阅事件类型放入前者,把订阅事件类型和订阅事件放在后者,订阅事件中包含订阅者和其对应的订阅方法。对于粘性事件的处理,我们一会会说。
现在就完成了注册(把订阅者,订阅方法,订阅事件类型保存了起来),下面我们就开始看注销的源码。
注销流程
注销的流程相对简单,注册的流程是向两个全局map中存放数据,注销的流程自然就是在两个全局map中删除数据。
注销的代码是:
EventBus.getDefault().unregister(this);
所以我们先看以下unregister()方法干了些什么。
unregister()
//这个方法传入的参数是订阅者(activity范围)
public synchronized void unregister(Object subscriber) {
// 根据订阅者,获取他所订阅的所有的订阅事件类型type
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
// 循环遍历,删去对应的数据
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
//数据删除之后,最后在typesBySubscriber中把此订阅者移除
typesBySubscriber.remove(subscriber);
}
}
这个方法逻辑总共有三步:
- 第一步:获取此订阅者所订阅的所有事件类型
- 第二步:根据类型,去删除对应数据,盲目猜测是去全局map subscriptionsByEventType中删除相应数据。
- 第三步:数据都删除了,最后在typesBySubscriber中把此订阅者移除。
逻辑说完了,我们看一下删除相应数据的unsubscribeByEventType(subscriber, eventType)方法具体做了什么。
unsubscribeByEventType(subscriber, eventType)
//这个方法传入参数是订阅者和订阅事件类型
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--;
}
}
}
}
这个方法主要是根据订阅事件类型,删除subscriptionsByEventType中所有的当前订阅者的订阅事件。
经过上面两个方法,我们就成功的清除了两个全局map中关于当前订阅者的所有订阅事件,至此我们就完成了注销的所有工作。
小结
发送和处理事件
发送事件的方法是:
EventBus.getDefault().post(new MessageEvent("MessageEvent"));
所以我们想了解发送事件,就要先看一下post这个方法:
post(event)
public void post(Object event) {
// PostingThreadState是存储了事件队列,线程模式等等信息的类,其是一个PostingThreadState 类型的ThreadLocal
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;
}
}
}
这个方法当中,主要是获取到PostingThreadState的对象,其是一个ThreadLocal,存储了发送的事件队列,线程模式等等存储信息的类,然后将待发送事件放入事件队列中,然后调用postSingleEvent(eventQueue.remove(0), 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);
}
//......
}
这个方法的逻辑就是,获取到发送事件所对应的类,然后通过lookupAllEventTypes获取eventClass的所有父类。然后通过轮循,对所有的类及父类进行发送事件。只要有一个类,有被订阅,则subscriptionFound为true。接下来又调用了postSingleEventForEventType方法:
postSingleEventForEventType()
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 获取事件对应的subscriptions集合
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;
}
这个方法中,我们通过订阅事件类型,在全局的2个map其中的subscriptionsByEventType中找到所有的订阅事件集合,然后进行遍历,调用postToSubscription(subscription, event, postingState.isMainThread)方法,根据不同的线程模式来处理:
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;
// ......
}
}
从这个方法开始就进入了处理事件的阶段,如果在当前线程可以处理,最后都会调用invokeSubscriber()方法处理。如果不是在当前线程处理,则就会调用Handler进行线程间通信,去调用我们创建的订阅方法。这里就不讲Handler的实现方式了,就只看一下当前线程可以运行的情况,所以我们直接看invokeSubscriber()方法:
invokeSubscriber()方法
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
最后也就是调用了一个Method.invoke()方法来对我们的方法进行处理,这里也就直接执行了我们订阅者的相对应的定义的方法了。
粘性事件的发送
粘性事件发送的代码:
EventBus.getDefault().postSticky(new MessageEvent("MessageEvent"));
我们看一下postSticky方法:
postSticky()
public void postSticky(Object event) {
synchronized (stickyEvents) {
// 对应每次的粘性事件,保证我们最后获取时拿到的都是最新的
stickyEvents.put(event.getClass(), event);
}
// 用锁机制存放后再发送,防止直接消费
post(event);
}
通过对事件上锁,然后调用的还是一个post()
方法,但是这里我们需要想起的是我们之前尚未分析的subscribe()中针对粘性事件做出处理的方法。
subscribe()—粘性事件的处理
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); // 1 -->
}
}
} else {
//根据eventType,从stickyEvents列表中获取特定的事件
Object stickyEvent = stickyEvents.get(eventType);
//分发事件
checkPostStickyEventToSubscription(newSubscription, stickyEvent); // 1 -->
}
}
这个方法中,会找到列表中所有的粘性事件,遍历找到与当前订阅的事件类型相同的粘性时间,然后发送。最后发送事件调用的是checkPostStickyEventToSubscription方法:
checkPostStickyEventToSubscription()
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.
// 这个方法在之前已经做过了分析
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}
最后发现最后调用的还是postToSubscription方法。