本文针对EventBus version 3.1.1进行解读
前言
框架方便了我们的开发,加速了产品研发的效率。有时候我们可能只会用框架,但是遇到问题的时候却束手无策。古人云,授之以鱼不如授之以渔,这个时候只是差了一个源码解析。只有深入之中,才得其法。
我们不重复造轮子不表示我们不需要知道轮子该怎么造及如何更好的造!
本文集将持续更新Android主流第三方框架的原理解析,敬请期待。
简介
由greenrobot组织贡献(该组织还贡献了greenDAO),一个Android事件发布/订阅轻量级框架。
EventBus可以代替Android传统的Intent,Handler,Broadcast或接口函数,在Fragment,Activity,Service线程之间传递数据,执行方法。
功能:通过解耦发布者和订阅者简化Android事件传递。
看下图:
有些文章会把发布-订阅模式与观察者模式混淆,其实它们之间还是有很大区别的。有兴趣的朋友可以查看这篇文章观察者模式和“发布-订阅”模式有区别吗?。
本文将从以下三个主要的主题来讲解:
- EventBus创建
- 注册为订阅者
- 消息发送
- 取消订阅
EventBus创建
先看代码:
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
一般我们会调用EventBus#getDefault(),懒汉模式(思考一下什么是饿汉模式)的单例模式,对于每一个不同的使用者来说,全局只有一个EventBus实例对象。
同时,我们还可以通过new EventBus()
的方式建立EventBus对象。但是一般不建议这么做。我们本来就是为了减低耦合度才使用的EventBus,如果每一个使用者采用这种方式,那么就会产生依赖注入、收发消息局限性等问题。当然,我们也可以构建自己的唯一EventBus对象,但是放在哪里好呢,肯定不能放在Activity这种有生命周期局限的组件内,那么也只能放在Application的初始化当中。
注册为订阅者
一般我们会采用以下方式在Activity中进行注册
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
EventBus.getDefault().register(this);
...
}
再抽丝源码之前我们先看看EventBus中几个重要的变量,有助于我们接下来的分析
private static final Map, List>> eventTypesCache = new HashMap<>();
private final Map, CopyOnWriteArrayList> subscriptionsByEventType;//事件类型为key,订阅者集合为value
private final Map
register()
public void register(Object subscriber) {
Class> subscriberClass = subscriber.getClass();
//查询订阅者已经订阅的事件集合
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);//真正开始注册流程
}
}
}
register()方法很简单,首先通过findSubscriberMethods()查找订阅的方法集合,再通过遍历对订阅的方法进行一一注册。
findSubscriberMethods()订阅方法的查找流程
List findSubscriberMethods(Class> subscriberClass) {
//从缓存中获取subscriberMethods
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//是否忽略注解器生成的MyEventBusIndex
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;
}
}
findSubscriberMethods()方法首先会从Map缓存中获取订阅方法集合。如果存在缓存,则直接返回。否则,会判断是否忽略ignoreGeneratedIndex。
插播一幅图:
Subscriber Index技术使得订阅者注册速度快了不只是一点点。原因很简单,通过编译时生成订阅者订阅方法的一些信息,这样就避免了运行时通过反射进行查找而造成的性能问题。Subscriber Index技术使用很简单,可以参见官方介绍Subscriber Index
ignoreGeneratedIndex的默认值为false,我们也可以通过EventBusBuilder重新初始化。
当ignoreGeneratedIndex为true时会通过findUsingReflection()进行查找,否则会通过findUsingInfo()进行查找。如果没有没有订阅方法,则抛出异常。如果有订阅方法,则将订阅方法加入到缓存当中。
findUsingInfo()
private List findUsingInfo(Class> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//获取订阅者信息,没有配置MyEventBusIndex返回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();
}
//回收处理findState,并返回订阅方法的List集合
return getMethodsAndRelease(findState);
}
在FindState里面,它保存了一些订阅者的方法以及对订阅方法的校验。通过initForSubscriber初始化了FindState的clazz属性。于是下面便进入while循环当中。通过getSubscriberInfo方法来获取订阅者信息。
在我们开始查找订阅方法的时候并没有忽略注解器为我们生成的索引MyEventBusIndex,如果我们通过EventBusBuilder配置了MyEventBusIndex,便会获取到subscriberInfo。而在MyEventBusIndex中会将订阅方法相关的信息存放在SubscriberMethodInfo类中,这个时候就不在需要通过注解进行获取订阅方法。
如果没有配置MyEventBusIndex,便会执行findUsingReflectionInSingleClass方法,将订阅方法保存到findState中。最后再通过getMethodsAndRelease方法对findState做回收处理并反回订阅方法的List集合。
对于MyEventBusIndex的配置它是一个可选项。它的执行过程在这里就不在进行详细的介绍,下面就来看一下如何通过反射来查找订阅方法,也就是看一下findUsingReflectionInSingleClass的执行过程。
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
//获取Method
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers(); //获取修饰符
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
//保存到findState对象当中
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, 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)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class> eventType = subscriberMethod.eventType;
//将subscriber和subscriberMethod封装成 Subscription
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//根据事件类型获取特定的 Subscription
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
//如果为null,说明该subscriber尚未注册该事件
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//如果不为null,并且包含了这个subscription 那么说明该subscriber已经注册了该事件,抛出异常
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//根据优先级来设置放进subscriptions的位置,优先级高的会先被通知
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;
}
}
//根据subscriber(订阅者)来获取它的所有订阅事件
List> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
//把订阅者、事件放进typesBySubscriber这个Map中
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//下面是对粘性事件的处理
if (subscriberMethod.sticky) {
//从EventBusBuilder可知,eventInheritance默认为true.
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).
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, Object> entry : entries) {
Class> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
//根据eventType,从stickyEvents列表中获取特定的事件
Object stickyEvent = stickyEvents.get(eventType);
//开始分发事件
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
消息发送
现在来看一下事件的发送过程。在获取到EventBus对象以后,通过post方法来进行对事件的提交,下面就来看一下整个post方法是如何对事件进行提交的。
public void post(Object event) {
//PostingThreadState保存着事件队列和线程状态信息
PostingThreadState postingState = currentPostingThreadState.get();
//获取事件队列,并将当前事件插入到事件队列中
List
在PostingThreadState中保存了事件队列,以及线程的一些状态信息。首先从PostingThreadState对象中取出事件队列,然后再将当前的事件插入到事件队列当中。最后将队列中的事件依次交由postSingleEvent方法进行处理,并移除该事件。下面就在进入postSingleEvent方法中看一下。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
//获取所有事件并存放在List中,这里表示事件存在继承关系,向上查找事件的父类
List> 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);
}
//找不到该事件时的异常处理
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
对于eventInheritance表示是否向上查找事件的父类。它的默认值为true,可以通过在EventBusBuilder中来进行配置。当eventInheritance为true时,则通过lookupAllEventTypes找到所有的父类事件并存发在List中,然后通过postSingleEventForEventType方法对事件逐一处理。下面就跟进到postSingleEventForEventType方法中查看一下。
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class> eventClass) {
//同步取出该事件对应的Subscription集合
CopyOnWriteArrayList 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;
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;
}
通过事件类型取出Subscription的List集合,然后调用了postToSubscription方法来处理事件并执行订阅方法。下面再来看一下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 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);
}
}
订阅方法的线程模式为MAIN的时候。提交事件的线程是主线程则直接执行invokeSubscriber方法。否则加入到mainThreadPoster对象的队列中,而mainThreadPoster对象他是HandlerPoster对象。HandlerPoster继承自Handler,也即是通过Handler将订阅方法切换到主线程执行。
当执行订阅方法需要在子线程中的时候。EventBus是通过Java中的newCachedThreadPool线程池来创建线程并执行的任务。BackgroundPoster和AsyncPoster都是一个Runable对象。而执行它们们run方法也是在enqueue方法中。
取消订阅
EventBus.getDefault().unregister(this);
public synchronized void unregister(Object subscriber) {
List> 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());
}
}
private void unsubscribeByEventType(Object subscriber, Class> eventType) {
List 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()和unsubscribeByEventType()主要对typesBySubscriber和subscriptionsByEventType进行操作,删除订阅者的信息。
总结
EventBus的执行过程实际上就是一个典型的发布订阅模式。通过对事件的发布与订阅,有效的为我们事件的发送者与接收者之间进行了解耦。