版權聲明:本文章原創於 RamboPan ,未經允許,請勿轉載。
源碼基於 EventBus 3.0.0
EventBus
上一篇我們從三個方法入手分析了 EventBus。從 EventBus 三個方法入手看源碼(一)
接着上一篇文章,我們大概瀏覽下 EventBus 的結構,發現代碼也並不多,那我們就接着繼續看看其他方法,主要從 public 的方法下手。
sticky()
大概晃了一下,發現其實上一節還有一個疑問,就是 @Subscribe 中的 sticky() 屬性有什麼作用 ?當時我們是直接跳過了。
現在從這個類的結構裏面,能看到 post() 方法還有個類似的 postSticky() 方法,那我們先去瞅瞅這個方法。
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<Class<?>, Object> stickyEvents;
EventBus(EventBusBuilder builder) {
……
stickyEvents = new ConcurrentHashMap<>();
}
能看到 stickyEvents 是一個 Map,並且有點眼熟。
對,在上一篇文章中我們剛好在 subscribe 略過了。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
……
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);
}
}
}
之前分析過 eventInheritance 爲 true ,stickyEvents.entrySet() 就是 Class<消息事件> 的集合。
通過 eventType.isAssignableFrom(candidateEventType) 判斷 stickySet 中的鍵是否與該次 subsribe 中綁定的 消息事件 是否爲同一個類,如果是的話,調用 checkPostStickyEventToSubscription()。
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 是重點,剛好也應證了這個邏輯。
/**
* 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 中加入如下代碼。
先註冊再發消息
@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)
@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)
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 註解中讀到的,現在我們來改改。
@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)
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()
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() 中有一段代碼。
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 的處理邏輯。
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();
/** For unit test primarily. */
public static void clearCaches() {
SubscriberMethodFinder.clearCaches();
eventTypesCache.clear();
}
static void clearCaches() {
METHOD_CACHE.clear();
}
從名字上來看,這個方法是清除緩存的。而且 METHOD_CACHE 這個名字看起來也好面熟,彷彿是在找帶有 @Subscribe 方法時見過。
那我們從 register() 中開始搜索。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> 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() 有關,如果是第一次註冊,就保存下來,如果取消了之後,不清除緩存,下次就直接獲取。
/** Unregisters the given subscriber from all event classes. */
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());
}
}
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
List<Subscription> 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() 中使用的,保存類與他的父類,上篇文章中簡單說了下,按照當前的使用習慣,這個變量並沒有多少發揮的價值,這裏也是簡單略過吧。
/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
synchronized (eventTypesCache) {
List<Class<?>> 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;
}
}
其他方法就沒什麼分析的空間了。
此分析純屬個人見解,如果有不對之處或者欠妥地方,歡迎指出一起討論。