上一篇我们针对
EventBus
的 注册 与 注销做了源码剖析,通过流程图和对源码的剖析,应该对上述流程有了一定了解.
接下来我们将会对Post事件发布与接收 和 postSticky事件发布与接收 展开剖析,我们将会提出几个问题,让我们带着问题去剖析这两个事件的流程。
我的EventBus系列的其他文章地址
EventBus系列『一』——注册与注销
EventBus系列『二』——Post与postSticky事件的发布与接收
EventBus系列『番外』——认真剖析 『PendingPostQueue』队列的实现思想
问题
Post事件
与postSticky事件
的使用场景
Post事件
与postSticky事件
的区别
postSticky事件
实现 粘性的原理
剖析
Post事件发布
我们先上一张
Post事件
发布流程图:
我们根据流程图的执行流程,开始我们的对源码的剖析:
public void post(Object event) {
//从ThreadLocal中获取PostingThreadState类实例
PostingThreadState postingState = currentPostingThreadState.get();
//获取PostingThreadState的属性
List
- 1.从ThreadLocal中获取PostingThreadState类实例
- 遍历队列,逐一对队列中的元素执行发布 参数中
eventQueue.remove(0)
方法会将队列下的指定位置的元素移除,并返回被移除的元素值
- 最后重置
postingState
状态属性
[ 1 ] 遍历队列,逐一对队列中的元素执行发布,进入postSingleEvent(eventQueue.remove(0), postingState)
方法
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
//获取事件的类型class
Class> eventClass = event.getClass();
boolean subscriptionFound = false;
//判定是否是继承事件
if (eventInheritance) {
//获取类型class的父类集合
List> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
//遍历类型class的父类集合
for (int h = 0; h < countTypes; h++) {
Class> clazz = eventTypes.get(h);
//[ 2] 为事件类型发布单个事件,返回的结果与 subscriptionFound 进行 [ 位或运算 ] (有1则为1),
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//为事件类型发布单个事件
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
//若发布的事件没有被订阅
if (!subscriptionFound) {
//是否打印没有订阅的信息
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
/**
*@重点讲解:此处做一次判定
* 1. 若正常发布的事件没有被订阅,则在此处将 当前 eventBust实例
* 和完成的 event事件封装成 NoSubscriberEvent类型,重新发送
*2.若没有被订阅的事件已经是封装了NoSubscriberEvent类型的事
*件,那么将不会再次发布了。
*/
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
// 当找不到已发布事件的订阅者时,EventBus将此事件发布到事件总线上
post(new NoSubscriberEvent(this, event));
}
}
}
- 获取事件类型class,判定当前事件是不是继承事件
- 2.若是继承事件,则获取类型class的父类集合,遍历父类集合, 为事件类型发布单个事件,返回的结果与 subscriptionFound 进行 [ 位或运算 ]
- 3.若不是继承事件,则为事件类型发布单个事件
- 判定发布的事件是否被订阅
- 若 没有被订阅,则 此处做一次判定.
若 正常发布的事件没有被订阅,则 在此处将 当前eventBust实例
和完成的event事件
封装成NoSubscriberEvent类型
,重新发送;
若 没有被订阅的事件已经是封装了NoSubscriberEvent类型
的事件, 则 将不会再次发布了
[ 2 ] 为事件类型发布单个事件 postSingleEventForEventType(event, postingState, eventClass)
方法
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 = false;
try {
//[ 3 ] 发布订阅事件
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;
}
- 通过注册类class从
subscriptionsByEventType
获取订阅信息集合
(提示:subscriptionsByEventType
集合只有在register注册
的时候才会执行put,此处是post发布事件
直接从中取值 ).- 2.做判定
subscriptionsByEventType
获取的数据为是否为Null
- 3.若为Null, 表明发布的事件还没有被订阅,也就是说事件是在执行
register注册
之前发布的,所以没有被订阅上,所以直接返回False
即可.- 4.若不为Null, 遍历订阅信息事件集合, 发布订阅事件
重点提示
- 这里若是发布的
Post事件
没有被订阅,则该事件就会 流转,即作废.- 若发布的
postSticky事件
没有被订阅,由于postSticky事件
是将完整的事件放入内存的,当执行register注册
时会从内存中获取该事件,然后包装成一个新的Post事件
进行发布,由于此时register注册
已执行完毕,所以postSticky事件
可以正常使用.
[ 3 ] 发布订阅事件 postToSubscription(subscription, event, postingState.isMainThread)
方法
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
//根据订阅事件的 threadMode类型 进行分发处理
switch (subscription.subscriberMethod.threadMode) {
case POSTING: // 默认的线程模式,在那个线程发送事件就在对应线程处理事件,避免了线程切换,效率高
//[ 3.1 ] 执行调用订阅者
invokeSubscriber(subscription, event);
break;
case MAIN: // 如在主线程(UI线程)发送事件,则直接在主线程处理事件;如果在子线程发送事件,则先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。
//判断是否在主线程发送分事件
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
//[3.2]将订阅事件放入主线程队列,依此处理事件
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED: // 无论在那个线程发送事件,都先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// 若主线程队列为空,则直接调用执行订阅者
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND: //如果在主线程发送事件,则先将事件入队列,然后通过线程池依次处理事件;如果在子线程发送事件,则直接在发送事件的线程处理事件。
if (isMainThread) {
//[ 3.3 ] 将订阅事件放入后台队列,依次调用执行
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC: //无论在那个线程发送事件,都将事件入队列,然后通过线程池处理。
// [ 3.4 ] 将订阅事件放入线程池队列,依此调用执行订阅者
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
- 根据订阅事件的 threadMode类型 进行分发处理
- 1.1. POSTING: 默认的线程模式,在那个线程发送事件就在对应线程处理事件,避免了线程切换,效率高
- 1.2 MAIN: 如在主线程(UI线程)发送事件,则直接在主线程处理事件;如果在子线程发送事件,则先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。
- 1.3 MAIN_ORDERED: 无论在那个线程发送事件,都先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件
- 1.4 BACKGROUND: 如果在主线程发送事件,则先将事件入队列,然后通过线程池依次处理事件;如果在子线程发送事件,则直接在发送事件的线程处理事件。
- 1.5 ASYNC: 无论在那个线程发送事件,都将事件入队列,然后通过线程池处理。
- 对事件进行分发后,判断决定将
Event事件
直接通过反射执行 或 放入相应队列中逐个执行。
postSticky粘性事件发布
通过对源码的分析我们可以发现
postSticky发布过程
与post发布过程
的区别,我们看源码:
public void postSticky(Object event) {
synchronized (stickyEvents) {
//[ 1 ] 将订阅事件类型class与事件关联,放入Map集合缓存
stickyEvents.put(event.getClass(), event);
}
//将给定的事件发布到事件总线
post(event);
}
可以看出
postSticky函数
在执行post函数
之前,执行了一次对完整Event事件
的Map集合
存储,而Map集合的特性
决定了在Key值
相同的情况下,存储的Value值
以最后一次放入的准,这也正符合postSticky事件
的 同样参数类型,以最后一次发布的事件为准 特性
Post 、postSticky事件接收
Post事件接收
发送
POST事件
一定要确保接收方页面执行过了register注册
,并且已存在对应的接收方法,即 被订阅完成后再执行事件发布,方可被正确接收
postSticky事件接收
postSticky事件
在发布阶段进行了一次对完整Event事件的事件的缓存。
若接收方此时还未执行register注册
,则 Event事件则将一直存储于内存中,直至接收方register注册
完成后,将会内存中获取完整Event事件,当做一个新Event事件发布到总线上,进而被正确获取。
我们将register注册
时对postSticky事件
特殊特殊处理源码粘出供大家理解:
若接收方已执行完register注册
,则直接进行发布
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
...
...
//判断当前
if (subscriberMethod.sticky) {
//判断是不是继承的事件
if (eventInheritance) {
//遍历粘性事件缓存集合
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 {
//根据订阅方法类型,从缓存集合获取对应的 粘性事件
Object stickyEvent = stickyEvents.get(eventType);
// @重点 发布到事件总线
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
问题回答
[ 1 ] Post事件
与 postSticky事件
的使用场景
Post
适用于向上传递,不适用于向下传递,即已完成register注册
操作后,再发布POST事件
。postSticky
适用于任何场景,即register注册
操作前后,都可进行postSticky事件
发布
[ 2 ] Post事件
与 postSticky事件
的区别
Post事件
必须在接收方完成register注册
操作后发布才能正常被接收到,否则无法被接收;而postSticky事件
则没有这样的限制要求。Post
发布的事件若没有被订阅的事件会将发生流转,即 作废,无效;而postSticky
发布的事件未被订阅,则将存储于内存中,当订阅者完成register注册
后从内存中取出,当做一个新事件进行发布。
[ 3 ] postSticky事件
实现 粘性的原理
postSticky
的原理是:postSticky
在发布时将完整的事件存储于全局Map集合 stickyEvents
中,而当接收方进行register注册
是,会对粘性事件做特殊处理,从stickyEvents
Map集合中取出相应的事件,交由postToSubscription
函数,进行重新发布.