EventBus原始碼分析(二):register方法儲存事件的訂閱者列表(2.4版本)

王世暉發表於2016-07-04

EventBus原始碼分析(一):入口函式提綱挈領(2.4版本)
EventBus原始碼分析(二):register方法儲存事件的訂閱者列表(2.4版本)
EventBus原始碼分析(三):post方法釋出事件【獲取事件的所有訂閱者,反射呼叫訂閱者事件處理方法】(2.4版本)
EventBus原始碼分析(四):執行緒模型分析(2.4版本)
EventBus原始碼解讀詳細註釋(1)register的幕後黑手
EventBus原始碼解讀詳細註釋(2)MainThread執行緒模型分析
EventBus原始碼解讀詳細註釋(3)PostThread、MainThread、BackgroundThread、Async四種執行緒模式的區別
EventBus原始碼解讀詳細註釋(4)register時重新整理的兩個map
EventBus原始碼解讀詳細註釋(5)事件訊息繼承性分析 eventInheritance含義
EventBus原始碼解讀詳細註釋(6)從事件釋出到事件處理,究竟發生了什麼!

EventBus維護了兩個重要的map:

  1. 事件到訂閱者列表的map,釋出事件的時候可以根據事件獲取訂閱者列表,這樣可以逐一反射呼叫訂閱者的事件處理方法。
  2. 訂閱者到事件列表的map,這樣訂閱者向EventBus解除註冊(unregister)的時候可以根據訂閱者獲取該訂閱者訂閱的所有事件,對每個事件分別解除註冊。

因此register註冊訂閱者的時候,EventBus會通過反射尋找訂閱者訂閱的所有事件,更新上述兩個map。如果是粘性sticky事件,在訂閱的時候就取出儲存的sticky事件直接傳送,這樣就做到了釋出者先發布事件,之後訂閱者訂閱事件,接收訂閱之前釋出的粘性事件。

    private synchronized void register(Object subscriber, boolean sticky, int priority) {
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod, sticky, priority);
        }
    }

可以看到register主要做了兩件事:

  1. 找到訂閱者訂閱的所有事件
  2. 對每個事件分別訂閱,更新map

通過反射找到訂閱者訂閱的所有事件

在register方法中,要更新兩個map,必須首先獲取訂閱者訂閱的所有事件。

    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) 

findSubscriberMethods方法傳入訂閱者的Class物件,返回該訂閱者訂閱的所有事件處理方法列表。
該方法程式碼比較長,分段進行閱讀。

        ...
        private static final Map<String, List<SubscriberMethod>> methodCache = 
        new HashMap<String, List<SubscriberMethod>>();
        ...
        String key = subscriberClass.getName();
        List<SubscriberMethod> subscriberMethods;
        synchronized (methodCache) {
            subscriberMethods = methodCache.get(key);
        }
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

因為反射查詢事件處理方法比較耗費效能,因此我們加入快取機制,通過靜態HashMap儲存訂閱者的事件處理方法。
先查詢快取,快取命中的話直接返回事件處理方法列表,否則通過反射重新查詢獲取。

     while (clazz != null) {
         ...
         clazz = clazz.getSuperclass();
     }

clazz.getSuperclass()方法會返回clazz父類的Class物件,如果clazz代表的類是根類Object、原生型別、介面或者Void則該方法返回null,因此EventBus不僅會儲存當前訂閱者訂閱的事件,還會儲存訂閱者的父類訂閱的事件。

            String name = clazz.getName();
            if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
                // Skip system classes, this just degrades performance
                break;
            }

為了效能考慮,直接跳過包名為java、javax和androd開頭的類,因為這些類是系統類,我們不會訂閱系統類為訂閱者,因此不需處理系統類。

   // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                String methodName = method.getName();
                if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
                    int modifiers = method.getModifiers();
                    if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                        Class<?>[] parameterTypes = method.getParameterTypes();
                        if (parameterTypes.length == 1) {
                            String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
                            ThreadMode threadMode;
                            if (modifierString.length() == 0) {
                                threadMode = ThreadMode.PostThread;
                            } else if (modifierString.equals("MainThread")) {
                                threadMode = ThreadMode.MainThread;
                            } else if (modifierString.equals("BackgroundThread")) {
                                threadMode = ThreadMode.BackgroundThread;
                            } else if (modifierString.equals("Async")) {
                                threadMode = ThreadMode.Async;
                            } else {
                                if (skipMethodVerificationForClasses.containsKey(clazz)) {
                                    continue;
                                } else {
                                    throw new EventBusException("Illegal onEvent method, check for typos: " + method);
                                }
                            }

接下來通過EventBus約定的規則去尋找事件處理方法
事件處理方法必須是onEvent開頭

    private static final String ON_EVENT_METHOD_NAME = "onEvent";
    ...
    if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
    ...

只能是public的非抽象(abstract)、非靜態(static)方法

    int modifiers = method.getModifiers();
    if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
    ...

事件處理方法只能有一個引數

    Class<?>[] parameterTypes = method.getParameterTypes();
    if (parameterTypes.length == 1) {
    ...

經過上述重重過濾,找到了事件處理方法,然後根據事件處理方法的命名規則,判定處理該事件的執行緒模型

     String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
     ThreadMode threadMode;
     if (modifierString.length() == 0) {
         threadMode = ThreadMode.PostThread;
     } else if (modifierString.equals("MainThread")) {
         threadMode = ThreadMode.MainThread;
     } else if (modifierString.equals("BackgroundThread")) {
         threadMode = ThreadMode.BackgroundThread;
     } else if (modifierString.equals("Async")) {
         threadMode = ThreadMode.Async;
     }

由此可知:

  1. onEvent方法的執行緒模型是PostThread,在釋出者執行緒處理事件
  2. onEventMainThread方法的執行緒模型是MainThread,在主執行緒處理事件
  3. onEventBackgroundThread方法的執行緒模型是BackgroundThread
  4. onEventAsync方法的執行緒模型是Async

BackgroundThread和Async執行緒模型的區別隨後會專門研究討論,此處不表。

       Class<?> eventType = parameterTypes[0];
       methodKeyBuilder.setLength(0);
       methodKeyBuilder.append(methodName);
       methodKeyBuilder.append('>').append(eventType.getName());
       String methodKey = methodKeyBuilder.toString();
       if (eventTypesFound.add(methodKey)) {
           // Only add if not already found in a sub class
           subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
       }

接著以”事件處理方法名>事件型別“為key,加入到一個set中

        HashSet<String> eventTypesFound = new HashSet<String>();

eventTypesFound這個set的主要作用是去重,如果子類覆寫了父類的事件處理方法,那麼EventBus只儲存子類的事件處理方法,父類的事件處理方法就像覆寫的意義那樣,被覆蓋掉。

     if (subscriberMethods.isEmpty()) {
         throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
                 + ON_EVENT_METHOD_NAME);
     } else {
         synchronized (methodCache) {
             methodCache.put(key, subscriberMethods);
         }
         return subscriberMethods;
     }

如果訂閱者沒有訂閱事件,也就是說沒有找到事件處理方法,那麼丟擲異常。
如果找到了該訂閱者的所有事件處理方法,在返回之前,要新增快取。

對每個事件進行單獨訂閱,更新兩個map

找到訂閱者訂閱的所有事件後,會通過subscribe方法對每個事件進行單獨訂閱。

      for (SubscriberMethod subscriberMethod : subscriberMethods) {
          subscribe(subscriber, subscriberMethod, sticky, priority);
      }

subscribe方法實際上是更新兩個map:

  1. 事件到訂閱者列表的map,釋出事件的時候可以根據事件獲取訂閱者列表,這樣可以逐一反射呼叫訂閱者的事件處理方法。
  2. 訂閱者到事件列表的map,這樣訂閱者向EventBus解除註冊(unregister)的時候可以根據訂閱者獲取該訂閱者訂閱的所有事件,對每個事件分別解除註冊。
    // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
        Class<?> eventType = subscriberMethod.eventType;
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<Subscription>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
        // subscriberMethod.method.setAccessible(true);

        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<Class<?>>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

        if (sticky) {
            Object stickyEvent;
            synchronized (stickyEvents) {
                stickyEvent = stickyEvents.get(eventType);
            }
            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());
            }
        }
    }

首先根據事件從map中獲取該事件的訂閱者列表,根據引數建立一個Subscription物件(封裝了訂閱者,事件處理方法和優先順序)

     Class<?> eventType = subscriberMethod.eventType;
     CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
     Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);

如果該事件沒有訂閱者列表的話,就新建一個訂閱者列表新增進map

        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<Subscription>();
            subscriptionsByEventType.put(eventType, subscriptions);
        }

否則判斷是否為重複訂閱,是的話丟擲異常。
接著根據新的訂閱者的優先順序將新訂閱者插入訂閱者列表的合適位置

        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

可見優先順序數值越大,越靠近列表的前部。
因此訂閱者列表是按照優先順序從大到小排隊的。
接著更新另外一個map,獲取該訂閱者訂閱的所有事件

        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<Class<?>>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

最後判斷是否為sticky事件,是的話就讀取儲存的sticky事件釋出出去

        if (sticky) {
            Object stickyEvent;
            synchronized (stickyEvents) {
                stickyEvent = stickyEvents.get(eventType);
            }
            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());
            }
        }

事件的釋出將在下一篇中繼續分析。

相關文章