Android事件总线(二)EventBus3.0源码解析
1.构造函数
当我们要调用EventBus的功能时,比如注册或者发送事件,总会调用EventBus.getDefault()来获取EventBus实例:
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
很明显这是一个单例模式,采用了双重检查模式 (DCL)。
接下来看看new EventBus()做了什么:
public EventBus() {
this(DEFAULT_BUILDER);
}
这里DEFAULT_BUILDER是默认的EventBusBuilder,用来构造EventBus:
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
this调用了EventBus另一个构造函数:
EventBus(EventBusBuilder builder) {
subscriptionsByEventType = new HashMap<>();
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>();
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), );
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : ;
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
logSubscriberExceptions = builder.logSubscriberExceptions;
logNoSubscriberMessages = builder.logNoSubscriberMessages;
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
throwSubscriberException = builder.throwSubscriberException;
eventInheritance = builder.eventInheritance;
executorService = builder.executorService;
}
2.订阅者注册
获取到EventBus后,便可以将订阅者注册到EventBus中,下面来看一下register方法:
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
// 用 subscriberMethodFinder 提供的方法,找到在 subscriber 这个类里面订阅的内容。
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
查找订阅方法
findSubscriberMethods找出一个SubscriberMethod的集合,也就是传进来的订阅者所有的订阅的方法,接下来遍历订阅者的订阅方法来完成订阅者的订阅操作。对于SubscriberMethod(订阅方法)类中,主要就是用保存订阅方法的Method对象、线程模式、事件类型、优先级、是否是粘性事件等属性。下面就来看一下findSubscriberMethods方法:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//从缓存中获取SubscriberMethod集合
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex
if (ignoreGeneratedIndex) {
//通过反射获取subscriberMethods
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
//在获得subscriberMethods以后,如果订阅者中不存在@Subscribe注解并且为public的订阅方法,则会抛出异常。
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;
}
}
首先从缓存中查找,如果找到了就立马返回。如果缓存中没有的话,则根据 ignoreGeneratedIndex 选择如何查找订阅方法,ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex。不了解MyEventBusIndex的同学可以查看【Bugly干货分享】老司机教你 “飙” EventBus 3这篇文章。最后,找到订阅方法后,放入缓存,以免下次继续查找。ignoreGeneratedIndex 默认就是false,可以通过EventBusBuilder来设置它的值。我们在项目中经常通过EventBus单例模式来获取默认的EventBus对象,也就是ignoreGeneratedIndex为false的情况,这种情况调用了findUsingInfo方法:
private List<SubscriberMethod> 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();
}
return getMethodsAndRelease(findState);
}
通过getSubscriberInfo方法来获取订阅者信息。在我们开始查找订阅方法的时候并没有忽略注解器为我们生成的索引MyEventBusIndex,如果我们通过EventBusBuilder配置了MyEventBusIndex,便会获取到subscriberInfo,调用subscriberInfo的getSubscriberMethods方法便可以得到订阅方法相关的信息,这个时候就不在需要通过注解进行获取订阅方法。如果没有配置MyEventBusIndex,便会执行findUsingReflectionInSingleClass方法,将订阅方法保存到findState中。最后再通过getMethodsAndRelease方法对findState做回收处理并反回订阅方法的List集合。
对于MyEventBusIndex的配置它是一个可选项,所以在这里就不在进行详细的介绍,下面就来看一下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) != && (modifiers & MODIFIERS_IGNORE) == ) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == ) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
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");
}
}
}
在这里主要是使用了Java的反射和对注解的解析。首先通过反射来获取订阅者中所有的方法。并根据方法的类型,参数和注解来找到订阅方法。找到订阅方法后将订阅方法相关信息保存到FindState当中。
订阅者的注册过程
在查找完所有的订阅方法以后便开始对所有的订阅方法进行注册:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
//根据订阅者和订阅方法构造一个订阅事件
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//获取当前订阅事件中Subscription的List集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//该事件对应的Subscription的List集合不存在,则重新创建并保存在subscriptionsByEventType中
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//订阅者已经注册则抛出EventBusException
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//遍历订阅事件,找到比subscriptions中订阅事件小的位置,然后插进去
int size = subscriptions.size();
for (int i = ; 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中
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);
}
}
}
订阅的代码主要就做了两件事,第一件事是将我们的订阅方法和订阅者封装到subscriptionsByEventType和typesBySubscriber中,subscriptionsByEventType是我们投递订阅事件的时候,就是根据我们的EventType找到我们的订阅事件,从而去分发事件,处理事件的;typesBySubscriber在调用unregister(this)的时候,根据订阅者找到EventType,又根据EventType找到订阅事件,从而对订阅者进行解绑。第二件事,如果是粘性事件的话,就立马投递、执行。
3.事件的发送
在获取到EventBus对象以后,可以通过post方法来进行对事件的提交:
public void post(Object event) {
//PostingThreadState保存着事件队列和线程状态信息
PostingThreadState postingState = currentPostingThreadState.get();
//获取事件队列,并将当前事插入到事件队列中
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
//处理队列中的所有事件
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
首先从PostingThreadState对象中取出事件队列,然后再将当前的事件插入到事件队列当中。最后将队列中的事件依次交由postSingleEvent方法进行处理,并移除该事件。来看看postSingleEvent方法里做了什么:
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
//eventInheritance表示是否向上查找事件的父类,默认为true
if (eventInheritance) {
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = ; 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) {
CopyOnWriteArrayList<Subscription> subscriptions;
//取出该事件对应的Subscription集合
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
//将该事件的event和对应的Subscription中的信息(包扩订阅者类和订阅方法)传递给postingState
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集合并遍历该集合将事件event和对应Subscription传递给postingState并调用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,提交事件的线程是主线程的话则通过反射,直接运行订阅的方法,如果不是主线程,我们需要mainThreadPoster将我们的订阅事件入队列,mainThreadPoster是HandlerPoster类型的继承自Handler,通过Handler将订阅方法切换到主线程执行。

4.订阅者取消注册
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 {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
typesBySubscriber我们在订阅者注册的过程中讲到过这个属性,他根据订阅者找到EventType,然后根据EventType和订阅者来得到订阅事件来对订阅者进行解绑。
5.核心架构与利弊
核心架构

从EventBus作者提供的图我们可以看到EventBus的核心架构,其实就是基于观察者模式来实现的,关于观察者模式可以查看设计模式(五)观察者模式这篇文章。
利与弊
EventBus好处比较明显,它能够解耦和,将业务和视图分离,代码实现比较容易。而且3.0后,我们可以通过apt预编译找到订阅者,避免了运行期间的反射处理解析,大大提高了效率。当然EventBus也会带来一些隐患和弊端,如果滥用的话会导致逻辑的分散并造成维护起来的困难。另外大量采用EventBus代码的可读性也会变差。
参考资料
EventBus3.0源码
EventBus3.0 源码解析
EventBus 利弊与源码解析
EventBus3.0源码解读
【Bugly干货分享】老司机教你 “飙” EventBus 3
Android事件总线(二)EventBus3.0源码解析的更多相关文章
- Android事件总线(四)源码解析otto
前言 上一篇文章中讲到了otto的用法,这一篇我们来讲一下otto的源码.可能有人觉得otto过时了,但是通过源码我们学习的是高手设计otto时的设计理念,这种设计理念是不过时的. otto各个类的作 ...
- EventBus3.0源码解析
本文主要介绍EventBus3.0的源码 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递. EventBus使用简单,并将事件发布和订阅充 ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- solr&lucene3.6.0源码解析(二)
上文描述了solr3.6.0怎么采用maven管理的方式在eclipse中搭建开发环境,在solr中,为了提高搜索性能,采用了缓存机制,这里描述的是LRU缓存,这里用到了 LinkedHashMap类 ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- Android进阶:五、RxJava2源码解析 2
上一篇文章Android进阶:四.RxJava2 源码解析 1里我们讲到Rxjava2 从创建一个事件到事件被观察的过程原理,这篇文章我们讲Rxjava2中链式调用的原理.本文不讲用法,仍然需要读者熟 ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- 深入理解Java AIO(二)—— AIO源码解析
深入理解Java AIO(二)—— AIO源码解析 这篇只是个占位符,占个位置,之后再详细写(这个之后可能是永远) 所以这里只简单说一下我看了个大概的实现原理,具体的等我之后更新(可能不会更新了) 当 ...
- solr&lucene3.6.0源码解析(四)
本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...
随机推荐
- MySQL slow_log表不能修改成innodb引擎
背景 从mysql.slow_log 获取慢查询日志很慢,该表是csv表,没有索引. 想添加索引来加速访问,而csv引擎不能添加索引(csv引擎存储是以逗号分割的文本来存储的),只能改存储引擎来添加索 ...
- TypeEncodings
官网链接: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Ar ...
- [POC]SuiteCRM 7.10.7 - 'record' SQL Injection
#################################################################### # Exploit Title: SuiteCRM - 're ...
- 一些oracle的经验
注:再写存储过程的时候,在for循环里要写begin和end,这样就可以写exception ,让这条错误数据回滚,然后记录错误日志,commit 关键字: oracle 存储过程 1.基本结构 CR ...
- 如何开始DDD(续)
上一篇针对用户注册案例简单介绍了如何使用 DDD,接下来我将继续针对这个例子做一下补充.先将User模型丰富起来,因为目前看上去他和贫血模型还没有啥大的区别. 首先还是由领域专家来说明业务,他提出了用 ...
- 关于一个div上下左右居中的css方法
1:通过position:absolute定位,上下左右的值都设为0,margin:auto:需要知道div的宽高 { width: 64px; height: 64px; border: 1px s ...
- Python:使用基于事件驱动的SAX解析XML
SAX的特点: 是基于事件的 API 在一个比 DOM 低的级别上操作 为您提供比 DOM 更多的控制 几乎总是比 DOM 更有效率 但不幸的是,需要比 DOM 更多的工作 基于对象和基于事件的接口 ...
- CentOS 6.5 网络服务器功能的实现②:运用光盘(镜像)制作一个本地yum源
在用Linux安装软件时(rpm安装方式),有时会出现“包依赖”的现象.因此,我们可以用yum工具来实现一次性安装所有rpm工具包的功能. 实例:在此服务器上用yum的方式安装DHCP服务和TFTP服 ...
- Windows系统环境下创建mysql主从数据库方法(双向主从复制)
创建mysql主从数据库方法(双向主从复制) (一)Windows系统下的MySQL主从复制(单向复制) (1)环境说明: 1,Mysql版本:mysql5.7.20(主从机mysql版本必须一致) ...
- 利用channel在goroutins之间控制同步和传递数据
在java等代码中,我们查询数据库的操作: sql = "select * from ...."; result = db.query(sql) for(item in resul ...