從 EventBus 三個方法入手看源碼(二)

版權聲明:本文章原創於 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);
            }
        }
    }
    

之前分析過 eventInheritancetruestickyEvents.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 只能每個 消息事件 類,保存最近的一個消息。現在再回頭看 @Subscribesticky() 的註釋。

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;
        }
    }
    

其他方法就沒什麼分析的空間了。

此分析純屬個人見解,如果有不對之處或者欠妥地方,歡迎指出一起討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章