版本
v3.2.0
源码+注释存放在
参考:
- 官网
- 这是一份详细的 EventBus 使用教程
- EventBus版本变更图
读前简介
1. EventBus中的各个角色
- 发布者
- 订阅者
- 订阅方法
- 事件
- 发布线程
- 订阅线程
- 事件Hash表
2.主要类功能及名词介绍
- SubscriberMethod:订阅方法的封装
- EventBus : EventBus框架的入口,提供 注册,反注册 ,发送消息,配置能力
- EventBusBuilder:EventBus的构建器
- SubscriberMethodFinder:订阅方法查找器 ,用于获取
- Subscription:作为一个订阅的封装 ,拥有订阅者 和 订阅方法
- HandlerPoster:一个Handler 如果 传入looper 是主线程 那就是主线程 handler
- 黏性事件:就是在发送事件之后再订阅该事件也能收到该事件,用于解决异步所带来的一些问题
源码阅读顺序
- 获取EventBus实例
- 注册 及 注册订阅方法
- 反注册
- 发送消息 及 接受消息
- 取消事件
- 黏性事件
- 线程切换
下面我们就按照这个顺序,依次看一下EventBus 都做了哪些事情
开始阅读
一. 获取EventBus实例
1. EventBus.getDefault()
EventBus.getDefault()
就是通过 DCL 方式 创建单例。通过这种方式获取的消息线路,我们称之为 ==消息总线==
static volatile EventBus defaultInstance;
//使用DCL 方式创建单例
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;
}
2. new EventBus()
除了使用 EventBus.getDefault()
获取单例 以外,EventBus 还支持通过 new
的方式创建新的EventBus 对象,不过不同的EventBus 对象对应着不同的 消息线路,也就是说 ==A== EventBus 不可能 接收到 ==B== EventBus 的 消息。所以如果总线压力过大,我们可以 通过多个EventBus 来分担 总线的压力.
3. new EventBus(EventBusBuilder builder)
除了使用默认构造这种简单的创建方式以外,EventBus 还支持通过 构建器来创建 实例。其实默认构造最终也是调用的构建器模式,只不过它的构建器也是默认的。如下代码所示
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
public EventBus() {
this(DEFAULT_BUILDER);
}
EventBus(EventBusBuilder builder) {
.....
}
下来我们看看怎么通过构建器创建EventBus 对象
EventBusBuilder builder = EventBus.builder();
builder.eventInheritance=false;
builder. ..
EventBus eventBus = builder.installDefaultEventBus();
eventBus. ..
其实就是通过 构建器 设置一些默认的值 ,然后在构建出一个 EventBus 对象。非常正统的构建器模式。下来我们看一下 EventBus 都给我们预留了那些 接口。可以让我们自行配置。
4. EventBusBuilder 配置
属性---含义---默认值的对应关系(具体功能介绍请看下方)
属性 | 含义 | 默认值 |
---|---|---|
logSubscriberExceptions | 订阅函数执行有异常时,打印异常信息 | true |
logNoSubscriberMessages | 是否打印没有 订阅函数 的log | true |
sendSubscriberExceptionEvent | 订阅函数执行出错 是是否发送 一个异常类型为 SubscriberExceptionEvent 的消息 方便统一常 | true |
sendNoSubscriberEvent | 没有对应订阅函数是发送一个类型为 NoSubscriberEvent 的消息 方便统一 | true |
throwSubscriberException | 订阅函数执行出错时抛出 EventBusException 异常,当为true时 logSubscriberExceptions,sendSubscriberExceptionEvent 这俩就没用了 | false |
eventInheritance | 是否触发 订阅函数形参 为消息类型的父类的订阅函数(官方描述 如果关闭会 提升20的速度,具体时间取决于 注册类的继承结构) | true |
ignoreGeneratedIndex | 忽略订阅者索引(既是存在订阅者索引,也强制使用反射获取订阅方法) | false |
strictMethodVerification | 是否严格验证订阅函数签名 | false |
executorService | 自定义线程池 | DEFAULT_EXECUTOR_SERVICE |
skipMethodVerificationForClasses | 跳过方法签名验证集合 | 无 |
subscriberInfoIndexes | 添加订阅者索引(不指定的话EventBus就会通过反射的方式获取 注册方法 列表) | 无 |
logger | EventBus 内部使用的 Logger | 无 |
具体功能解析
- logSubscriberExceptions:订阅函数执行有异常时,打印异常信息
当为true是如果订阅函数内部异常的话 会在控制台打印 error 类型的log,如下log,如果为false则不打印。
2020-06-15 11:58:11.380 26367-26367/org.greenrobot.eventbusperf E/EventBus: Could not dispatch event: class java.lang.String to subscribing class class org.greenrobot.debug.BadExceptionSubscriber
java.lang.RuntimeException: Bad
at org.greenrobot.debug.BadExceptionSubscriber.onEvent(BadExceptionSubscriber.java:8)
at java.lang.reflect.Method.invoke(Native Method)
at org.greenrobot.eventbus.EventBus.invokeSubscriber(EventBus.java:511)
at org.greenrobot.eventbus.EventBus.postToSubscription(EventBus.java:434)
.....
- logNoSubscriberMessages : 是否打印没有监听者的log
当为true时:如果没有对应的监听者会在 控制台打印 如下log。如果为false就没有
2020-06-15 11:02:43.656 14516-14516/org.greenrobot.eventbusperf D/EventBus: No subscribers registered for event class java.lang.String
2020-06-15 11:02:43.656 14516-14516/org.greenrobot.eventbusperf D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
- sendSubscriberExceptionEvent : 订阅函数执行出错 是是否发送 一个异常类型为 SubscriberExceptionEvent 的消息 方便统一处理异常
当为true时遇到 订阅函数执行有异常的时候会发送一个异常类型为 SubscriberExceptionEvent 的消息。这时候我们可以进行降级处理 或者 上报异常 操作如下
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(SubscriberExceptionEvent event) {
Log.e(TAG,"订阅函数有异常 "+event.toString());
}
- sendNoSubscriberEvent: 没有对应订阅函数是发送一个类型为 NoSubscriberEvent 的消息 。这时候我们可以进行降级处理 或者 上报异常 操作如下
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(NoSubscriberEvent event) {
Log.e(TAG,"没有订阅函数 "+event.toString());
}
- throwSubscriberException:订阅函数执行出错时抛出 EventBusException 异常,当为true时 logSubscriberExceptions,sendSubscriberExceptionEvent 这俩就没用了,因为在 EventBus 中如下代码中
throwSubscriberException
的优先级比logSubscriberExceptions
,sendSubscriberExceptionEvent
这俩高
private void handleSubscriberException(Subscription subscription, Object event, Throwable cause) {
if (event instanceof SubscriberExceptionEvent) {
....
} else {
if (throwSubscriberException) {
throw new EventBusException("Invoking subscriber failed", cause);
}
if (logSubscriberExceptions) {
logger.log(Level.SEVERE, "Could not dispatch event: " + event.getClass() + " to subscribing class "
+ subscription.subscriber.getClass(), cause);
}
if (sendSubscriberExceptionEvent) {
SubscriberExceptionEvent exEvent = new SubscriberExceptionEvent(this, cause, event,
subscription.subscriber);
post(exEvent);
}
}
}
- eventInheritance :是否触发 订阅函数形参 为消息类型的父类的订阅函数(有点绕,看下面例子就很清晰)官方描述 如果关闭会 提升20的速度,具体时间取决于 注册类的继承结构。
==注: BEventBean 继承了 AEventBean==// BEventBean 继承了 AEventBean public class BEventBean extends AEventBean { public String bMsg; } @Subscribe(threadMode = ThreadMode.MAIN) public void onEventStr(AEventBean msg) { log(msg.toString()); } @Subscribe(threadMode = ThreadMode.MAIN) public void onEventStr(BEventBean msg) { toast(msg.toString()); }
- 当 eventInheritance 为true的时候上面 两个订阅方法都会被执行。
- 当 eventInheritance 为false的时候只有形参为 BEventBean 会被执行。
- ignoreGeneratedIndex:即使存在订阅者索引,也强制使用反射获取订阅方法。
这个标示位使用在SubscriberMethodFinder.findSubscriberMethods
方法中.
List findSubscriberMethods(Class> subscriberClass) {
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
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;
}
}
- strictMethodVerification: 是否严格验证订阅函数签名,默认为false,当为true时 EventBus 当检测到 注册方法的签名不合法时会抛出异常,代码位于
SubscriberMethodFinder.findUsingReflectionInSingleClass
- executorService:可以设置项目自己的线程池 如果有的话 ,避免 项目中有多个箱尺寸在,浪费资源
- skipMethodVerificationForClasses :跳过方法签名验证集合,通过 skipMethodVerificationFor(Class> clazz) 可以添加
- subscriberInfoIndexes:添加订阅者索引,方便我们自定义和扩展
- logger:EventBus 内部使用的 Logger ,可以使用 logger()方法进行设置
二. 注册 及 注册订阅方法
主要作用
- 查找订阅者中的订阅方法并缓存到
EventBus.subscriptionsByEventType
这个map中 - 如果是黏性订阅方法 订阅以后直接 调用。以实现先 发消息后注册 也能接收到 事件的功能
源码梳理
1. EventBus.register
//注册 订阅者
public void register(Object subscriber) {
//获取接受者的字节码对象
Class> subscriberClass = subscriber.getClass();
//查找 subscriber 中的订阅方法 ,这里没有加锁 说明可以并发进行 【详见1.1】
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
//遍历所有订阅方法
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//预处理每个 订阅方法 【详见1.2】
subscribe(subscriber, subscriberMethod);
}
}
}
1.1 SubscriberMethodFinder.findSubscriberMethods
主要作用是 在订阅者中查找订阅方法列表 并进行返回,查找方式分为两种
- 反射
- 订阅索引
使用订阅索引会提高EventBus的运行速度,具体原理和如何使用参考 ==知识点3,4==
List findSubscriberMethods(Class> subscriberClass) {
//从缓存中获取
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
//命中缓存直接返回
return subscriberMethods;
}
//是否强制使用反射获取订阅方法(可在EventBusBuilder 中配置)
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;
}
}
1.2 EventBus.subscribe
主要有两个
- 将订阅关系缓存到 subscriptionsByEventType 和 typesBySubscriber中
- 如果是黏性事件 直接调用 订阅方法
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
....
//添加 订阅方法形参类型 和 一个订阅 的映射
subscriptionsByEventType.put(eventType, subscriptions);
....
//添加 订阅者 和 订阅方法形参类型 的映射
typesBySubscriber.put(subscriber, subscribedEvents);
....
//如果是黏性订阅方法 订阅以后直接 调用,
if (subscriberMethod.sticky) {
//是否触发 订阅函数形参 为消息类型的父类的订阅函数,可在 EventBusBuilder 中配置
if (eventInheritance) {//是
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, Object> entry : entries) {
Class> candidateEventType = entry.getKey();
//candidateEventType 是否是 eventType 的子类
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {//否
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
三. 反注册
主要作用
主要作用是 将在订阅过程中形成的订阅关系同 缓存中清除 ,清除的位置有两个
- subscriptionsByEventType
- typesBySubscriber
源码梳理
1. EventBus.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());
}
}
三. 发送消息 及 接受消息
主要作用
发送事件并触发订阅方法
源码梳理
1. EventBus.post
public void post(Object event) {
//获取到当前线程的 PostingThreadState(用 ThreadLocal 保存 所以每个 线程只有一个)
PostingThreadState postingState = currentPostingThreadState.get();
//获取当前线程的 事件队列
List
1.1 EventBus.postSingleEvent
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
//获取事件类型
Class> eventClass = event.getClass();
boolean subscriptionFound = false;
//是否 触发 event 父类的 注册方法
if (eventInheritance) {
List> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class> clazz = eventTypes.get(h);
//【详见1.2】
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//【详见1.2】
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {//没找到订阅方法
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
//发送一个 NoSubscriberEvent
post(new NoSubscriberEvent(this, event));
}
}
}
1.2 EventBus.postSingleEventForEventType
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class> eventClass) {
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;
try {
//触发所有订阅方法 【详见1.3】
postToSubscription(subscription, event, postingState.isMainThread);
// 执行 cancelEventDelivery 后 postingState.canceled 会为true
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
//如果执行了 cancelEventDelivery 就会退出,不在 触发其他订阅方法
if (aborted) {
break;
}
}
return true;
}
return false;
}
1.3 EventBus.postToSubscription
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING://直接触发
invokeSubscriber(subscription, event);
break;
case MAIN://如果不是主线程 切换到主线程触发
if (isMainThread) {
//详见【1.4】
invokeSubscriber(subscription, event);
} else {//不是主线程的话 会 通过handle调度到主线程执行
//详见【1.5】
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(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);
}
}
1.4 EventBus.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);
}
}
1.5 HandlerPoster.enqueue
public void enqueue(Subscription subscription, Object event) {
//将 subscription , event 封装为 PendingPost
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
//加入到队列中
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
//使用handler 发送一个消息 【详见1.6】
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
1.6 HandlerPoster.handleMessage
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
//取出一个 事件
PendingPost pendingPost = queue.poll();
....
//执行 订阅方法 【详见1.4】
eventBus.invokeSubscriber(pendingPost);
....
}
} finally {
handlerActive = rescheduled;
}
}
四. 取消事件
主要作用
取消以后就不会触发 后面的 订阅方法
源码梳理
1 HandlerPoster.cancelEventDelivery
public void cancelEventDelivery(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
//只有 threadMode = POSTING 时有效
if (!postingState.isPosting) {
throw new EventBusException(
"This method may only be called from inside event handling methods on the posting thread");
} else if (event == null) {
throw new EventBusException("Event may not be null");
} else if (postingState.event != event) {
throw new EventBusException("Only the currently handled event may be aborted");
} else if (postingState.subscription.subscriberMethod.threadMode != ThreadMode.POSTING) {
throw new EventBusException(" event handlers may only abort the incoming event");
}
//更改 canceled 为true
postingState.canceled = true;
}
其实只是将 postingState.canceled 标志位 设置为 true,生效的地方参见 【==发送消息 及 接受消息 中1.2 postSingleEventForEventType==】
五. 黏性事件
主要作用
添加一个黏性事件,用于解决异步的一些问题
源码梳理
1 HandlerPoster.postSticky
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// 先触发一次
post(event);
}
发送黏性时间后 会先将事件保存到 stickyEvents
中,然后立马触发一次。当以后有黏性 订阅方法订阅后 会立即执行这个黏性事件,这也是为啥 黏性事件可以先发送再注册 也能接收到的原理。==黏性事件触发位置参考 【注册 及 注册订阅方法中1.2 EventBus.subscribe】==
六. 线程切换
主要作用
通过不同的 ThreadMode 指定 线程
源码梳理
【参考 发送消息 及 接受消息 中 1.3 EventBus.postToSubscription】
- POSTING :默认值,那个线程发送消息就是那个线程接受消息
- MAIN:指定主线程接受消息
- MAIN_ORDERED:也是指定主线程接受消息,不过如果前一个也是main_ordered 则需要等前一个执行完成后才执行
- BACKGROUND:指定后台线程(只有一个 ),处理如保存到数据库等操作。
- ASYNC:接受是始终重启进程进行操作,一般是用于比较消耗时间的任务
总结
流程分析
- 获取实例步骤 过程中我们可以对EventBus做一些配置
- 订阅 步骤只是对订阅者及订阅方法映射的缓存,如果是黏性事件则立即触发 订阅方法
- 反订阅 步骤就是 删除订阅步骤缓存的映射
- 发送消息 及 接受消息 步骤就是: 通过订阅步骤中生成的缓存 查找到 对应方法并 对于不同的 ThreadMode ,做不同的线程切换,最后都调用对应的 订阅方法。
- 取消事件就相当于 停止 下一个 订阅方法的执行。
ThreadMode的几种模式
- POSTING :默认值,那个线程发送消息就是那个线程接受消息
- MAIN:指定主线程接受消息
- MAIN_ORDERED:也是指定主线程接受消息,不过如果前一个也是main_ordered 则需要等前一个执行完成后才执行
- BACKGROUND:指定后台线程(只有一个 ),处理如保存到数据库等操作。
- ASYNC:接受是始终重启进程进行操作,一般是用于比较消耗时间的任务
知识点
==其实 Java中也有观察者模式的实现(Observer,Observable),其工作原理和 EventBus很像,只不过 EventBus 使用起来更加方便而且具有线程切换等优点。==
在某些情况下,比如 Activity中 getDeclaredMethods 比 getMethods 快,因为 getDeclaredMethods 只获取自身的方法(public、protected、private),而getMethods 会向上查找 所有父类的方法(public)。
查找订阅方法重如果我们没有手动设置过 EventBusBuilder 的 subscriberInfoIndexes 那么就会通过反射获取 注册方法 列表, 所以我们再使用EventBus 的时候最好通过builder指定一下 subscriberInfoIndexes,毕竟 反射 是比较耗时的。代码如下
EventBus eventBus = EventBus.builder()
.addIndex(new MyEventBusIndex())
.installDefaultEventBus();
-
SubscriberMethodFinder
中 使用反射获取 订阅方法列表和 通过 订阅索引获取 订阅方法列表 本质的区别是前者是在运行期 才组织关系,而 后者是在编译器 就已经确定。所以使用后者会 提高 运行速度。