以前发在 CSDN 上, blog.csdn.net/u014443348/…
掘金氛围感觉更好一点,打算慢慢转过来。
源码基于 EventBus 3.0.0
EventBus
上一篇我们从三个方法入手分析了 EventBus。从 EventBus 三个方法入手看源码(一)
接着上一篇文章,我们大概浏览下 EventBus 的结构,发现代码也并不多,那我们就接着继续看看其他方法,主要从 public 的方法下手。
sticky()
大概晃了一下,发现其实上一节还有一个疑问,就是 @Subscribe 中的 sticky() 属性有什么作用 ?当时我们是直接跳过了。
现在从这个类的结构里面,能看到 post()
方法还有个类似的 postSticky()
方法,那我们先去瞅瞅这个方法。
java 代码解读复制代码
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
private final Map, Object> stickyEvents;
EventBus(EventBusBuilder builder) {
……
stickyEvents = new ConcurrentHashMap<>();
}
能看到 stickyEvents 是一个 Map,并且有点眼熟。
对,在上一篇文章中我们刚好在 subscribe
略过了。
java 代码解读复制代码
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);
}
}
}
之前分析过 eventInheritance 为 true ,*stickyEvents.entrySet() * 就是 Class<消息事件> 的集合。
通过 eventType.isAssignableFrom(candidateEventType)
判断 stickySet 中的键是否与该次 subsribe
中绑定的 消息事件 是否为同一个类,如果是的话,调用 checkPostStickyEventToSubscription()
。
java 代码解读复制代码
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
……
case MAIN:
……
case BACKGROUND:
……
case ASYNC:
……
}
}
是不是突然线索都联系上了:如果是粘性的事件,那么会先放在一个 Set 当中,如果调用 subscribe
时,将会把这个事件消费掉。
那什么情况需要出现这种用法 ? 我们对比 post()
,按照官方的提示,需要先订阅,再发消息。
那如果没订阅发消息,就没有对应的消息处理,如果我需要在订阅之前发的消息,在订阅时仍然可以处理要怎么做 ? 不就刚好符合这个处理逻辑嘛,真是机智。
需要注意:stickyEvents 是一个 Map 结构,那么一般 Map.put
的实现,会返回一个值。 如果返回 null 一般认为是插入成功,如果返回是非空值,则是取出的之前的数据。
那么就是说,我们的 stickyEvents 只能每个 消息事件 类,保存最近的一个消息。现在再回头看 @Subscribe 中 sticky() 的注释。
the most recent 是重点,刚好也应证了这个逻辑。
java 代码解读复制代码
/**
* If true, delivers the most recent sticky event (posted with
* {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
*/
boolean sticky() default false;
好了,现在让我们先用代码来验证下。Activity 中加入如下代码。
先注册再发消息
java 代码解读复制代码
@Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
Log.d("EventBus", "register:");
}
@Override
protected void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
Log.d("EventBus", "un register:");
}
@Override
protected void onResume() {
super.onResume();
test();
}
private void test() {
EventData eventData = new EventData();
eventData.name = "hello";
eventData.number = 123;
Log.d("EventBus", "post event:");
EventBus.getDefault().post(eventData);
}
//默认是 POSTING 模式,也可以不写括号内容
@Subscribe(threadMode = ThreadMode.POSTING)
public void onReceiveEvent(EventData eventData){
Log.d("EventBus", "eventData : onReceiveEvent :" + eventData);
}
public static class EventData {
public String name;
public int number;
@Override
public String toString() {
return "EventData{" +
"name='" + name + ''' +
", number=" + number +
'}';
}
}
测试结果:
com.example.xxx D/EventBus: register:
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: eventData : onReceiveEvent :EventData{name=‘hello’, number=123}
按照正常流程使用时没有问题的,现在把发消息移到 onStart()
,注册放到 onResume()
中,来看下结果。
先发消息再注册(sticky = false)
java 代码解读复制代码
@Override
protected void onStart() {
super.onStart();
test();
Log.d("EventBus", "register:");
}
@Override
protected void onResume() {
super.onResume();
EventBus.getDefault().register(this);
}
测试结果:
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: register:
如之前代码分析一样,EventBus 也提示,没有对应的订阅,那我们现在把 test()
中 post()
方法改成 postSticky()
看看是不是也如之前分析一样。
先发消息再注册(sticky = true)
java 代码解读复制代码
private void test() {
EventData eventData = new EventData();
eventData.name = "hello";
eventData.number = 123;
Log.d("EventBus", "post event:");
EventBus.getDefault().postSticky(eventData);
}
测试结果:
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: register:
咦 …… 怎么和刚才一样,感觉没生效,不对啊,之前我们分析这么合理。
回头检查检查,发现我们是使用了 postSticky ,添加进之前说的数组中了,但是注册的时候没有对应处理,回头想想,应该有个 if (subscriberMethod.sticky)
的判断,这个值是在 @Subscribe 注解中读到的,现在我们来改改。
java 代码解读复制代码
@Subscribe(threadMode = ThreadMode.POSTING,sticky = true)
public void onReceiveEvent(EventData eventData){
Log.d("EventBus", "eventData : onReceiveEvent :" + eventData);
}
测试结果:
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: eventData : onReceiveEvent :EventData{name=‘hello’, number=123}
com.example.xxx D/EventBus: register:
现在对了,果然分析没有错,还有一个要验证的问题,就是如果注册之前发了两个粘性事件,是不是只会收到最近那个 ? 现在来改改发送消息。
发送两次消息(sticky = true)
java 代码解读复制代码
private void test() {
EventData eventData = new EventData();
eventData.name = "hello old";
eventData.number = 123;
Log.d("EventBus", "post event:");
EventBus.getDefault().postSticky(eventData);
EventData eventData2 = new EventData();
eventData2.name = "hello new";
eventData2.number = 321;
EventBus.getDefault().postSticky(eventData2);
}
测试结果
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: eventData : onReceiveEvent :EventData{name=‘hello new’, number=321}
com.example.xxx D/EventBus: register:
结果如我们所料,只收到了一个最新的消息,EventBus 也因为发了两次事件,所以提示了两轮。
接着然后我们来看看 cancelEventDelivery()
这个方法。
cancelEventDelivery()
java 代码解读复制代码
public void cancelEventDelivery(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
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");
}
postingState.canceled = true;
}
从方法的名字上来看,意思是取消对于某个事件的处理,但是在上一篇文章中关于 post()
中有一段代码。
java 代码解读复制代码
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
……
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(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
从这段代码中能看到 isPosting 的作用,貌似是在从队列中发出事件时,作为一个正在发出标记,如果上次没发送完,就有新的 post()
,只是先放进队列而已。
而每次发送完毕后,会将 isPosting 至为 false ,等待下次调用时再进行判断。那这个 cancelEventDelivery()
使用第一步就是判断 isPosting 才可以走下一步,感觉不太妥当,因为可能有放入队列但是没有执行 postSingleEvent()
的消息。个人认为是除了把 cancel 标志位改过之后,还需要把 eventQueue
中的消息对比之后删除比较合理。
那接下来看看 cancel 的处理逻辑。
java 代码解读复制代码
public void post(Object event) {
……
postSingleEvent(eventQueue.remove(0), postingState);
}
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
……
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class> eventClass) {
……
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
……
boolean aborted = false;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
//如果 canel 后 aborted = true,就结束执行循环。
if (aborted) {
break;
}
}
return true;
}
return false;
}
cancelEventDelivery()
方法也分析完了,还有一个方法也可以说说。
clearCaches();
java 代码解读复制代码
/** For unit test primarily. */
public static void clearCaches() {
SubscriberMethodFinder.clearCaches();
eventTypesCache.clear();
}
static void clearCaches() {
METHOD_CACHE.clear();
}
从名字上来看,这个方法是清除缓存的。而且 METHOD_CACHE 这个名字看起来也好面熟,仿佛是在找带有 @Subscribe 方法时见过。
那我们从 register()
中开始搜索。
java 代码解读复制代码
List findSubscriberMethods(Class> subscriberClass) {
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
……
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;
}
}
记得上篇文章,按着我们第一次走的逻辑,METHOD_CACHE.get(subscriberClass) == null
,那既然有非 null 的时候,可以大胆猜测下。
是不是与 register()
与 unregister()
有关,如果是第一次注册,就保存下来,如果取消了之后,不清除缓存,下次就直接获取。
java 代码解读复制代码
/** Unregisters the given subscriber from all event classes. */
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 {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
private void unsubscribeByEventType(Object subscriber, Class> eventType) {
List subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
果然对应方法都没有对 MOETHOD_CACHE 进行清理,那么也验证了猜想,如果第一次注册,则正常获取,如果是取消注册后,第二次注册,那么就从缓存中获取。
eventTypesCache 这个值是在 lookupAllEventTypes()
中使用的,保存类与他的父类,上篇文章中简单说了下,按照当前的使用习惯,这个变量并没有多少发挥的价值,这里也是简单略过吧。
java 代码解读复制代码
/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
private static List> lookupAllEventTypes(Class> eventClass) {
synchronized (eventTypesCache) {
List> eventTypes = eventTypesCache.get(eventClass);
if (eventTypes == null) {
eventTypes = new ArrayList<>();
Class> clazz = eventClass;
while (clazz != null) {
eventTypes.add(clazz);
addInterfaces(eventTypes, clazz.getInterfaces());
clazz = clazz.getSuperclass();
}
eventTypesCache.put(eventClass, eventTypes);
}
return eventTypes;
}
}
其他方法就没什么分析的空间了。
此分析纯属个人见解,如果有不对之处或者欠妥地方,欢迎指出一起讨论。
评论记录:
回复评论: