1. 程式人生 > >EventBus 超詳細的原始碼分析學習

EventBus 超詳細的原始碼分析學習

前言

EventBus已經使用了那麼久啦 ,但是一直都沒有去了解過其中內部的機制,後來雖然看了一些部落格,但是介紹的內容雖然看過了,但是還是不能很清晰的知道內部實現原理。所以本著 紙上得來終覺淺 絕知此事要躬行 的原則,決定親自去看一看原始碼。

內容部分

註冊方法分析

//註冊的類的註冊的事件的方法集合
private final SubscriberMethodFinder subscriberMethodFinder;
//註冊方法,這裡會儲存你註冊的物件,裡所有的方法(註冊監聽的方法)  
public void register(Object subscriber)
{ Class<?> subscriberClass = subscriber.getClass(); //這裡是以類為key獲取所有該類裡的方法。(註冊監聽的方法) List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod :
subscriberMethods) { //對所有的方法遍歷進行subscribe(建立訂閱) subscribe(subscriber, subscriberMethod); } } }

這裡遍歷訂閱過程需要在同步鎖裡進行,為了防止重複註冊,導致訊息接收重複。

//SubscriberMethod 類記錄了訂閱方法的資訊
  	final Method method;
    final ThreadMode threadMode;
    final Class<?
> eventType; final int priority; final boolean sticky; /** Used for efficient comparison */ String methodString;

subscribe方法內進行一系列的操作,這裡主要的工作如下:

// Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //事件型別,應該解釋為事件型別。 這個型別是你註冊的方法裡的引數類
        Class<?> eventType = subscriberMethod.eventType;
        //這裡是把方法和方法的持有物件關聯起來
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //通過類名字的這個key獲取所有該類下的方法,如果沒有見加入到map中
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            //每個方法都放到map中
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            //如果已經註冊過了,就丟擲異常,只允許註冊一次。
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            //這裡對加入的事件進行排序了
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }
	//該類裡註冊了集中事件型別
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
//是粘性麼?
        if (subscriberMethod.sticky) {
            //繼承關係的要處理父類和子類
            if (eventInheritance) {
                // Existing sticky events of all subclasses of eventType have to be considered.
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                //粘性事件map
                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);
            }
        }
    }

以上就是註冊方法

總結出來:

  1. 獲取註冊類下所有的註冊事件的方法,如activity註冊了event的監聽,就是獲取到所有該activity下添加註解標記的方法。

  2. 將每個方法放到不同的列表中進行儲存,儲存的為map。key為事件型別,value為所有註冊該事件的方法的包裝類(Subscription)。類中中包含了一些必要資訊。如該方法的持有者。(將註冊了EventBus的類裡註冊不同的EventType的方法放到不同的map中儲存。key為EventType,value為一個Subscriber的列表)

  3. 後續對事件的優先順序進行調整,就是調整事件在list中的index

  4. 將類中的所有註冊的事件也統計起來,key為類,value為裡面的事件集合,typesBySubscriber這個map中是儲存了每個Subscriber中訂閱的EventType型別

  5. 粘性事件單獨儲存,不做過多介紹

以上為註冊eventbus所做的工作,

這裡留一個問題,這個註冊方法呼叫時機是如何?是在類初始化的時候呼叫?


普通事件傳送流程分析

post是傳送事件的入口,所以開始從post的方法開始分析,程式碼如下:

public void post(Object event) {
        //獲取當前的執行緒
        PostingThreadState postingState = currentPostingThreadState.get();
        //拿到當前執行緒的佇列
        List<Object> eventQueue = postingState.eventQueue;
        //新增事件進入佇列
        eventQueue.add(event);
        //不是傳送
        if (!postingState.isPosting) {
            //標記為主執行緒
            postingState.isMainThread = isMainThread();
            //傳送中
            postingState.isPosting = true;
            //取消傳送
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                //類似handler的loop機制,無限的取訊息
                while (!eventQueue.isEmpty()) {
                    //一直從0位置開始取訊息
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                //異常就切換執行緒,取消傳送
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

上面的PostingThreadState記錄一些常用的狀態內容。

主要內容是傳送的內容postSingleEvent()方法的執行。

下面是對該方法的一些解讀,註釋的很詳細了:


    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        //傳送發訊息的類的位元組碼
        Class<?> eventClass = event.getClass();
        //預設找不到訂閱者
        boolean subscriptionFound = false;
        //預設找父類和子類
        if (eventInheritance) {
            //查詢所有的實現事件型別的類包括父類,找到實現了這個事件的所有類
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            //拿到總的數量
            int countTypes = eventTypes.size();

            for (int h = 0; h < countTypes; h++) {
                //拿到註冊該post事件類的位元組碼了
                Class<?> clazz = eventTypes.get(h);
                //呼叫post方法,找到接收該事件的訂閱者了
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            //呼叫post方法,找到接收該事件的訂閱者了
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        //沒找到的情況傳送一個NoSubscriberEvent事件代替
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

上面一系列操作後,繼續呼叫後續方法,我們快找到真正呼叫的地方了,繼續呼叫postSingleEventForEventType()方法,這裡會返回訊息傳送成功還是失敗。Subscription是比較關鍵的資訊類,裡面記錄了註冊了該事件的所有的類,為後續我們去挨個呼叫註冊類使用。

 private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            //通過類的位元組碼,拿到所有的註冊事件的類,解釋一下,通過你傳送的一個event事件,來拿到所有註冊了這個事件的類
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            //開始迴圈去投遞事件了
            for (Subscription subscription : subscriptions) {
                //把事件狀態類裡的引數,都附上值。
                postingState.event = event;
                postingState.subscription = subscription;
                //預設是失敗的傳送
                boolean aborted = false;
                try {
                    //真正呼叫的地方
                    postToSubscription(subscription, event, postingState.isMainThread);
                    //預設取消傳送為false
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    break;
                }
            }
            return true;
        }
        return false;
    }

這裡的一系列操作繼續向下呼叫postToSubscription方法,這個方法比較簡單,主要是根據事件的所處於的執行緒來進行不同的分發。程式碼如下

      private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                //直接就呼叫方法了,當前執行緒
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                //主執行緒的呼叫,如當前執行緒不是,需要切換執行緒(通過handle來做)
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                //有序的,通過handle來做
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                //後臺的,通過Runnable來實現的
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                //非同步的,通過Runnable來實現的
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

上面程式碼分的型別比較多,但實際上劃分只有兩種,一種是常規的傳送,一種是特殊處理的傳送。特殊處理包括主執行緒,非同步執行緒,後臺執行緒等。

這裡我們可能遇到過一個問題,就是在自執行緒中傳送的訊息,如果不在接受的地方表明主執行緒,會報錯。

重點來了啊,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);
        }
    }

結束啦

上面這些關鍵程式碼,就完成了一次普通事件的分發流程,其中沒有過多介紹細節的實現。

至於粘性事件分發,和普通事件,就差了一個收到事件後需要通知佇列,因為如果沒有通知,佇列的內的粘性訊息會一直處於傳送狀態。

有問題歡迎糾正,謝謝啦

如果對你有幫助就點個贊把,你的鼓勵是我前進的動力。