以下图表来自:codeKK
EventBus GitHub项目源码传送门:https://github.com/greenrobot/EventBus
功能介绍
EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递,这里的事件可以理解为消息,本文中统一称为事件。事件传递既可用于 Android 四大组件间通讯,也可以用户异步线程和主线程间通讯等等。
传统的事件传递方式包括:Handler、BroadCastReceiver、Interface 回调,相比之下 EventBus 的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦。
概念梳理
1、Event:事件,相当于我们使用Handler时的Message,一般我们自定义一个类来包装,这个类就是我们下面要提到的事件类型(EventType)。例如:
class UIEvent{
int id;
Object data;
}
事件分为一般事件和 Sticky 事件,相对于一般事件,Sticky 事件不同之处在于,当事件发布后,再有订阅者开始订阅该类型事件,依然能收到该类型事件最近一个 Sticky 事件。
2、Subscriber:订阅某种事件类型的对象,我们使用EventBus时,要在我们的类中注册对象:
EventBus.getDefault().register(this);
也要记得在合适时机反注册:
EventBus.getDefault().unregister(this);
注册EventBus以后,该类成为一个订阅者,当有发布者(Publisher)发布这种类型的事件时,EventBus会按一定规则执行订阅者的onEvent函数(事件响应函数)。订阅者存在优先级,优先级高的订阅者可以取消事件继续向优先级低的订阅者分发,默认所有订阅者优先级都为 0。
这里的onEvent响应函数包括:
onEvent(Object obj){};//在Publisher所在线程执行响应函数
onEventMainThread(Object obj){};//在主线程执行
onEventBackgroundThread(Object obj){};//在唯一后台排队执行
onEventAsync(Object obj){};//另开线程异步执行,适用于长耗时操作
我们可以根据具体需求,选择合适的响应函数,这里需要注意:
A、响应函数名字必须是上面四个中的一个。
B、响应函数只能有一个参数,也将是我们以后post的对象。
我们将在下面的findSubscriberMethods源码解析中详细解释原因。
3、Publisher:发布某种类型事件的对象。例如:
EventBus.Post(new UIEvent(1, "gk"));
我们不关心发布者是谁,我们只关心发布的事件。这里我发布了一个事件类型为UIEvent的事件,那么所有注册EventBus的Subscriber都会收到该事件,然后根据事件类型UIEvent,筛选出需要执行的onEvent响应函数。
如果我们需要指定某个对象执行响应函数,那就需要在Event对象中加入唯一标识,比如我这里的id。
使用流程
先贴上EventBus的类设计图:
接着我主要从register和post两方面谈谈整个流程。
注册
源码如下:
private void register(Object subscriber, String methodName, boolean sticky) {
List subscriberMethods = this.subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
methodName);
for (SubscriberMethod subscriberMethod : subscriberMethods)
subscribe(subscriber, subscriberMethod, sticky);
}
整个过程简述为:SubscriberMethodFinder对象用findSubscriberMethods方法根据注册者和响应方法名找到注册者的所有subscriberMethod信息,如果没有,则添加并缓存。然后把这些subscriberMethod信息跟注册者一一建立关联。
值得一提的是,register的第二个参数methodName默认是:onEvent
也就是说我们的响应函数要以onEvent开头
接下来我们看看分解操作:
我们先看看subscriberMethod都包含哪些信息:
final class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
String methodString;
……
}
method:响应函数的名字,即我们上面提到的四种响应函数。
threadMode:线程类型,我们看看它长什么样:
public enum ThreadMode{
PostThread,
MainThread,
BackgroundThread,
Async;
}
细心的同学会发现,它跟我们的响应函数长得极像且一一对应。的确是这样,我们在注册以后,EventBus会根据我们实现的响应函数名称,自动选择对应的线程类型。
eventType:事件类型,即最上面楼主举例的UIEvent。
methodString:响应函数名字 +事件类型名构建的字符串,用于比较两个subscriberMethod对象是否相等。
小结:
我们给一个对象注册EventBus后,SubscriberMethodFinder对象会根据我们的注册者,找到:
1、响应函数
2、执行响应函数所在的线程
3、响应事件类型
然后我们看看SubscriberMethodFinder中的findSubscriberMethods方法,回顾上面所说,这个方法会返回注册者的所有SubscriberMethod 信息。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass, String eventMethodName) {
String key = subscriberClass.getName() + '.' + eventMethodName;
List subscriberMethods;
//优先读缓存,如果有缓存纪录,直接返回
synchronized (methodCache) {
subscriberMethods = (List) methodCache.get(key);
}
if (subscriberMethods != null) {
return subscriberMethods;
}
//如果缓存中没有,则遍历注册者的每个函数并递归查找父类
List subscriberMethods = new ArrayList();
Class clazz = subscriberClass;
HashSet eventTypesFound = new HashSet();
StringBuilder methodKeyBuilder = new StringBuilder();
while (clazz != null) {
//遍历前提条件:注册者不能是以java. javax. android. 开头的类
String name = clazz.getName();
if ((name.startsWith("java.")) || (name.startsWith("javax.")) || (name.startsWith("android."))) {
break;
}
//开始遍历注册者每个方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
String methodName = method.getName();
//遍历规则1:方法是以"onEvent"开头
if (methodName.startsWith(eventMethodName)) {
Class[] parameterTypes = method.getParameterTypes();
//遍历规则2:方法的参数只有1个
if (parameterTypes.length == 1) {
String modifierString = methodName.substring(eventMethodName.length());
ThreadMode threadMode;
//如果方法名为"onEvent",线程模式(threadMode)为PostThread,即发布者所在线程
if (modifierString.length() == 0) {
threadMode = ThreadMode.PostThread;
} else {
ThreadMode threadMode;
//如果方法名为"onEventMainThread",线程模式(threadMode)为MainThread,即主线程
if (modifierString.equals("MainThread")) {
threadMode = ThreadMode.MainThread;
} else {
ThreadMode threadMode;
// 如果方法名为"onEventBackgroundThread",
// 线程模式(threadMode)BackgroundThread,即后台程
if (modifierString.equals("BackgroundThread")) {
threadMode = ThreadMode.BackgroundThread;
} else {
ThreadMode threadMode;
// 如果方法名为"onEventAsync",线程模式(threadMode)Async,即异步
if (modifierString.equals("Async")) {
threadMode = ThreadMode.Async;
} else {
//如果方法是以onEvent开头的其他方法,注册者又不在忽略名单里,则抛出异常
//至此,就很明白了,为何注册者内部的响应函数必须是上面说过的四个中的一个
if (skipMethodNameVerificationForClasses.containsKey(clazz)) {
continue;
}
throw new EventBusException("Illegal onEvent method, check for typos: " + method);
}
}
}
}
ThreadMode threadMode;
Class eventType = parameterTypes[0];
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(methodName);
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
//把响应函数名、线程模式、参数类型一起构造成一个SubscriberMethod对象,保存
if (eventTypesFound.add(methodKey)) {
subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
}
}
}
}
//遍历完注册者以后,再遍历其父类,规则同上
clazz = clazz.getSuperclass();
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass + " has no methods called " + eventMethodName);
}
//最后把subscriberMethods缓存
synchronized (methodCache) {
methodCache.put(key, subscriberMethods);
}
return subscriberMethods;
}
至此,我们获得了注册者所有的subscriberMethod信息,下面就要开始真正的注册了。再次回顾下源码:
private void register(Object subscriber, String methodName, boolean sticky) {
List subscriberMethods = this.subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
methodName);
for (SubscriberMethod subscriberMethod : subscriberMethods)
subscribe(subscriber, subscriberMethod, sticky);
}
现在我们进入 subscribe(subscriber, subscriberMethod, sticky)方法看一看:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky) {
this.subscribed = true;
Class eventType = subscriberMethod.eventType;
//1、通过subscriptionsByEventType得到该事件类型所有订阅者
CopyOnWriteArrayList subscriptions = (CopyOnWriteArrayList) this.subscriptionsByEventType.get(eventType);
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//如果之前没有该事件类型的订阅信息,则新加进去
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList();
this.subscriptionsByEventType.put(eventType, subscriptions);
} else {
//判断之前是否注册过
for (Subscription subscription : subscriptions) {
if (subscription.equals(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " +
eventType);
}
}
}
subscriberMethod.method.setAccessible(true);
subscriptions.add(newSubscription);
//2、在typesBySubscriber中得到当前订阅者订阅的所有事件队列,将此事件保存到队列typesBySubscriber中,用于后续取消订阅;
List subscribedEvents = (List) this.typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList();
this.typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//3、检查这个事件是否是Sticky事件,如果是则从stickyEvents事件保存队列中取出该事件类型最后一个事件发送给当前订阅者。
if (sticky) {
Object stickyEvent;
synchronized (this.stickyEvents) {
stickyEvent = this.stickyEvents.get(eventType);
}
Object stickyEvent;
if (stickyEvent != null)
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}
现在我们应该明白了,其实EventBus的注册,核心是两点:
1、找到该事件类型(Event)的所有注册者(Subcriber),把当前注册者添加进去
2、找到该注册者注册的所有事件类型(EventType),把当前的新事件类型添加进去
至此,注册过程才算真正完成,接下来我们看看post过程。
发布事件
相比register过程,post就比较简单了:
public void post(Object event) {
//得到当前线程的事件队列
List eventQueue = (List) this.currentThreadEventQueue.get();
eventQueue.add(event);
BooleanWrapper isPosting = (BooleanWrapper) this.currentThreadIsPosting.get();
//如果当前事件队列正在分发,不作处理
if (isPosting.value) {
return;
}
boolean isMainThread = Looper.getMainLooper() == Looper.myLooper();
isPosting.value = true;
try {
//否则开始循环事件队列
while (!eventQueue.isEmpty())
//post核心
postSingleEvent(eventQueue.remove(0), isMainThread);
} finally {
isPosting.value = false;
}
}
我们主要看看postSingleEvent方法:
private void postSingleEvent(Object event, boolean isMainThread) throws Error {
Class eventClass = event.getClass();
//找到该事件的所有父类和接口
List eventTypes = findEventTypes(eventClass);
boolean subscriptionFound = false;
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class clazz = (Class) eventTypes.get(h);
CopyOnWriteArrayList subscriptions;
synchronized (this) {
//获得每个事件的订阅者信息
subscriptions = (CopyOnWriteArrayList) this.subscriptionsByEventType.get(clazz);
}
CopyOnWriteArrayList subscriptions;
if (subscriptions != null) {
for (Subscription subscription : subscriptions) {
//发布给每个订阅者
postToSubscription(subscription, event, isMainThread);
}
subscriptionFound = true;
}
}
if (!subscriptionFound) {
Log.d(TAG, "No subscripers registered for event " + eventClass);
if ((eventClass != NoSubscriberEvent.class) && (eventClass != SubscriberExceptionEvent.class))
post(new NoSubscriberEvent(this, event));
}
}
我们继续看核心代码postToSubscription方法:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
//根据订阅者信息的线程模式,用反射,执行响应函数方法
switch (subscription.subscriberMethod.threadMode.ordinal()) {
case 1://发布者所在线程
invokeSubscriber(subscription, event);
break;
case 2://主线程
if (isMainThread)
invokeSubscriber(subscription, event);
else {
this.mainThreadPoster.enqueue(subscription, event);
}
break;
case 3: //后台线程
if (isMainThread)
this.backgroundPoster.enqueue(subscription, event);
else {
invokeSubscriber(subscription, event);
}
break;
case 4: //异步
this.asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
至此我们看到了发布事件的真面目,说白了就是,EventBus根据发布事件的事件类型,找到我们注册的响应函数;我们的响应函数会根据响应函数名,自动判断以何种线程模式执行自己。这就给我们使用者带来很大方便,使得我们可以只用关注响应函数的实现。
最后我们再看看这些概念:
1、HandlerPoster:事件主线程处理,对应ThreadMode.MainThread。继承自 Handler,enqueue 函数将事件放到队列中,并利用 handler 发送 message,handleMessage 函数从队列中取事件,invoke 事件响应函数处理。
2、AsyncPoster:事件异步线程处理,对应ThreadMode.Async,继承自 Runnable。enqueue 函数将事件放到队列中,并调用线程池执行当前任务,在 run 函数从队列中取事件,invoke 事件响应函数处理。
3、BackgroundPoster:事件 Background 处理,对应ThreadMode.BackgroundThread,继承自 Runnable。enqueue 函数将事件放到队列中,并调用线程池执行当前任务,在 run 函数从队列中取事件,invoke 事件响应函数处理。与 AsyncPoster.java 不同的是,BackgroundPoster 中的任务只在同一个线程中依次执行,而不是并发执行。
4、PendingPost:订阅者和事件信息实体类,并含有同一队列中指向下一个对象的指针。通过缓存存储不用的对象,减少下次创建的性能消耗。
5、PendingPostQueue:通过 head 和 tail 指针维护一个PendingPost队列。HandlerPoster、AsyncPoster、BackgroundPoster 都包含一个此队列实例,表示各自的订阅者及事件信息队列,在事件到来时进入队列,处理时从队列中取出一个元素进行处理。
后记
到此为止,EventBus源码就分析完了,这篇源码分析是博主参照codeKK的分析,读完EventBus源码,得出的记录贴,一方面加深下自己的印象,一方面以希望能给别人带来一些不一样的感悟吧。