本文基于EventBus3.1.1进行源码分析,以发送一个正常事件和粘性事件为例,探索EventBus工作的整个过程。你也可以直接下载demo同步运行调试,Gif示例如下:
gif中首先展示了发送一个LoginSuccessEvent的正常事件,在MainActivity和SecondActivity中都有订阅,这里主要展示一对多的场景;接着分别以正常方式和粘性方式发送了一个RegisterSuccessEvent事件,看看在GoToLoginActivity中有怎样不同的表现。
本文的思路是先分析注册和注销的流程,也就是订阅和解订阅;再分析发布正常事件和发布粘性事件的流程。
1. 注册和注销的流程分析
EventBus.getDefault().register(this)
EventBus.getDefault().unregister(this)
1.1. register
首先通过EventBus.getDefault()拿到实例对象,源码如下所示,
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
这是一种双重校验的懒汉式单例,双重校验机制只会在第一次创建实例时有锁的介入,一旦实例创建成功,下次再获取实例就不会进入锁块了。还有一点需要注意务必要用volatile关键字修饰defaultInstance变量,保证在操作defaultInstance对象时,都是从内存中加载最新的状态。后续通过getDefault()拿到的都是defaultInstance这个实例对象了。接着看看regitster()干了什么,源码如下所示:
public void register(Object subscriber) {
//拿到订阅者的Class实例对象
Class> subscriberClass = subscriber.getClass();
//找到该订阅者的所有订阅方法
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//发起订阅,保存订阅者与订阅方法的关系
subscribe(subscriber, subscriberMethod);
}
}
}
首先会拿到订阅者的Class实例对象,然后通过subscriberMethodFinder去查找该类所有的订阅方法,subscriberMethodFinder这个实例对象从命名就能看出是订阅方法查找器;最后利用synchronized保证线程安全便利发起订阅,保存订阅者与订阅方法的之间的关系。下图是debug register()的截图标注说明:
接下来我们详细分析findSubscriberMethods(subscriberClass)和subscribe(subscriber, subscriberMethod)这两个方法。
1.1.1. findSubscriberMethods(subscriberClass)
List findSubscriberMethods(Class> subscriberClass) {
//从缓存中拿取订阅者的所有订阅方法
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//是否忽略设置的索引,索引目的是把在注册时需要遍历订阅者所有方法的行为,提前到在编译时完成,
//在编译时apt插件通过EventBusAnnotationProcessor分析注解,并利用注解标识的相关类的信息去生成相关的类
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方法,首先会去缓存中找,如找到则直接return subscriberMethods,否则接着往下看看是否忽略设置的索引,默认false,此时会进入到findUsingInfo通过索引查找,最后判断subscriberMethods是不是空,不空的话则将订阅者和订阅方法放入缓存中。整个逻辑非常简单:先在缓存中找订阅方法集合列表,找不到再通过反射或者索引找,最后还是没找到,则抛出EventBusException,否则将订阅者和订阅方法放入缓存供下次使用。接下来我们看下findUsingInfo的源码,如下所示:
private List findUsingInfo(Class> subscriberClass) {
//FindState这个静态内部类中存放寻找方法时所需的临时变量
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) {
//checkAdd是为了避免在父类中找到的方法是被子类重写的,此时应该保证回调时执行子类的方法
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
//则通过反射的方式查找订阅方法信息
findUsingReflectionInSingleClass(findState);
}
//findState移动到该类的父类中,准备下次便利父类的订阅方法信息
findState.moveToSuperclass();
}
//释放findState并返回订阅者方法
return getMethodsAndRelease(findState);
}
进入findUsingInfo方法,通过FindState存放临时变量,然后遍历子类及其父类的订阅方法集合列表,最后返回该集合列表。由于demo中没有采用索引预生成订阅方法信息,虽然上面的ignoreGeneratedIndex进入了索引查找分支,但最终还是会通过findUsingReflectionInSingleClass方法反射查找该类的所有订阅方法。下面是findUsingReflectionInSingleClass的源码:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
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注解
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class> eventType = parameterTypes[0];
//两层检查:1检查事件类型 2检查方法的完整签名
if (findState.checkAdd(method, eventType)) {
//拿到线程模型
ThreadMode threadMode = subscribeAnnotation.threadMode();
//创建SubscriberMethod,并放到findState.subscriberMethods中
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");
}
}
}
进入findUsingReflectionInSingleClass方法,主要逻辑是通过反射拿到findState.clazz的所有声明方法methods,然后遍历methods,判断method的入参长度是否为1,判断method的注解是否为Subscribe,判断method的事件类型以及完整签名,最后创建SubscriberMethod放到findState中。
至此查找订阅方法的流程完毕,不关注过多细节的话,流程就是:findSubscriberMethods方法代码段中的注释信息,即先从缓存中拿取订阅者的所有订阅方法,如没找到则通过反射或者索引方式继续找,找到了则更新缓存并返回,否则抛出异常。
1.1.2. subscribe(subscriber, subscriberMethod)
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//拿到事件类型,就是你自己定义的Event,比如LoginSuccessEvent
Class> eventType = subscriberMethod.eventType;
//用订阅者和订阅方法构造Subscription,即Subscription描述了订阅者和订阅方法之间的关系
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//从subscriptionsByEventType这个Map中获取对应事件类型的subscriptions集合
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
//如未获取到,则创建一个新的
subscriptions = new CopyOnWriteArrayList<>();
//用eventType为key,subscriptions为value存放到Map集合subscriptionsByEventType中
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> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
//用subscriber为key,subscribedEvents为value存放到Map集合typesBySubscriber中
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//判断是否为粘性事件
if (subscriberMethod.sticky) {
//事件是否继承标识
if (eventInheritance) {
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, Object> entry : entries) {
Class> candidateEventType = entry.getKey();
//有两个Class类型的类象,一个是调用isAssignableFrom方法的类对象(后称对象a),以及方法中作为参数的这个类对象(称之为对象b),这两个对象如果满足以下条件则返回true,否则返回false:
//a对象所对应类信息是b对象所对应的类信息的父类或者是父接口,简单理解即a是b的父类或接口
//a对象所对应类信息与b对象所对应的类信息相同,简单理解即a和b为同一个类或同一个接口
//判断eventType是否与candidateEventType拥有一致的类型信息或者为继承关系
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
//拿到粘性事件
Object stickyEvent = stickyEvents.get(eventType);
//执行粘性事件
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
拿到订阅方法后,用Subscription描述订阅者和订阅方法之间的关系,并构造出一个实例对象newSubscription,然后在subscriptionsByEventType这个Map集合中查找eventType对应的subscriptions集合。subscriptionsByEventType这个Map以eventType为key,subscriptions为value,它保存了事件类型与subscriptions集合之间的关系,通过查找这个集合,可以知道某个事件对应的所有订阅者以及订阅方法。然后对比newSubscription的优先级,再插入到subscriptions,这里可以保证优先级高的订阅方法先被执行。接着维护一个typesBySubscriber的Map集合,这个集合主要是用于判断某个订阅者是否已被注册,避免重复注册。最后判断该订阅方法是否为粘性方法,如果是的话,则直接发送粘性事件给到订阅者,而不必再等待外部调用post。
下面是debug跳到SecondActivity时,发起注册的图示,可以看到SecondActivity中定于了LoginSuccessEvent事件,订阅方法是processLoginSuccessForNotify;而在MainActivity中也订阅了LoginSuccessEvent事件,订阅方法是processLoginSuccess,所以它们一同被维护到了subscriptionsByEventType集合中。
总结:注册事件时,先获取到EventBus的实例对象,然后调用register方法,先找到该订阅者里面的所有订阅方法(findSubscriberMethods),然后绑定事件类型、订阅者与订阅方法之间的关系,即维护好Map集合subscriptionsByEventType。
1.2. unregister
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());
}
}
注销的流程相对简单,通过unsubscribeByEventType方法维护下Map集合subscriptionsByEventType,遍历移除该订阅者下的订阅事件。下面是unsubscribeByEventType的源码:
private void unsubscribeByEventType(Object subscriber, Class> eventType) {
//拿到该事件的subscriptions集合
List subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
//判断该subscription对应的订阅者是不是跟要注销的订阅者一致
if (subscription.subscriber == subscriber) {
subscription.active = false;
//移除该subscription
subscriptions.remove(i);
i--;
size--;
}
}
}
}
2. 发布事件
发布事件时,并不需要你先去调用register方法注册,这点在新手中很容易出错。发布事件时,也需要先拿到EventBus实例,然后再调用post或者postSticky方法。
2.1. post
//post方法源码
public void post(Object event) {
//拿到PostingThreadState实例对象,用于存放发布时的一些变量
PostingThreadState postingState = currentPostingThreadState.get();
//拿到事件队列
List
进入post方法,主要就是将事件类型加入到事件队列中,然后遍历队列,调用postSingleEvent逐个发布事件。而在postSingleEvent中,会拿到事件的所有父类存放到eventTypes中,再遍历eventTypes逐个调用postSingleEventForEventType。这里的流程都非常简单,接着看源码:
//postSingleEventForEventType源码
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class> eventClass) {
CopyOnWriteArrayList subscriptions;
synchronized (this) {
//从Map集合subscriptionsByEventType中查找事件对应的subscriptions
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
//遍历subscriptions
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;
}
//postToSubscription源码
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
//根据线程模型进入不同的分支
switch (subscription.subscriberMethod.threadMode) {
...
case MAIN:
if (isMainThread) {
//反射调用订阅者中的方法
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
...
}
}
postSingleEventForEventType中最主要的事情就是从subscriptionsByEventType这个Map集合中取出对应事件的subscriptions集合,然后遍历它再逐个发送事件。进入到postToSubscription中后,根据subscriberMethod中的threadMode进入不同的分支,再反射调用订阅者中对应事件的对应订阅方法。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);
}
}
当进入到SecondActivity页面,点击按钮时,会依次反射MainActivity中的processLoginSuccess,再反射SecondActivity中的processLoginSuccessForNotify,下面是debug按钮点击是invokeSubscriber的图示说明:
总结:post这一过程相比register的过程还要简单,主要就是从subscriptionsByEventType这个Map集合中查找事件的subscriptions集合,然后再反射执行订阅方法。是的,整个过程就是如此简单。
2.2. postSticky
public void postSticky(Object event) {
//保存event到Map集合stickyEvents中
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
//发布事件
post(event);
}
postSticky与post的区别在于postSticky中将事件保存到了Map集合stickyEvents中,然后再发布事件,仅此而已,就是这么“粗暴直接”。这里有两处入口会执行粘性事件,第一处是postSticky,这里会反射执行该粘性事件的所有订阅方法,第二处其实上面分析register时就提到过了,在注册订阅者时就会判断该订阅者下的订阅方法是否为粘性,是的话就会立即执行订阅方法。
3. 一点感想
EventBus的原理其实是利用观察者模式,维护了一套观察者(订阅者)与被观察者(发布者所发布的事件)之间的Map集合。当发起订阅时(即register),就会以订阅者以及订阅方法为value,事件为key维护到Map中。当发布事件时,就会在Map集合中查找该事件对应的订阅者以及订阅方法,找到了就反射调用订阅方法,此时有可能某些订阅者是没有被创建或者被销毁了的,那么此时反射失败,EventBus框架就会内部消化掉这个异常。下面这张图我在官网图示的基础上增加了自己的一些理解和注释:
上面我只是分析了在主线程中发布,主线程中消费事件的流程,EventBus还有几个线程模型可供选择,分别是:POSTING、MAIN、MAIN_ORDERED、BACKGROUND、ASYNC,这里我不再一一赘述,有兴趣的可以自行debug调试。
当我们在维护自己的基础库供他人使用的时候,如果涉及到要实时通知功能的,可以借鉴EventBus的设计思路,EventBus的使用时非常简洁的,对外暴露的接口也异常简单,这对于设计框架供别人使用时很重要的一个技能,如果提供的东西使用起来很复杂,务必会增加他们的学习成本,从而导致基础库难以推广。还可以借鉴EventBus的单例模式控制好自己框架的实例数量,以及用FindState类这种保存临时变量这种思想来优雅的书写代码。
再有一个就是反射是很重要的一个技能,作为框架开发以及基础库开发,很有可能会要用到,所以掌握好反射是必须的。