Greenrobot-EventBus源碼學習(五)

EventBus 深刻學習五之註冊

訂閱者的註冊 + 消息推送java

1. 註冊

先貼出註冊代碼, 能夠可到和 Guava 相比沒什麼大的區別, 主要的點在內部實現上,一個是如何獲取註冊信息;一個是如何保存註冊關係git

/**
     * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
     * are no longer interested in receiving events.
     * <p/>
     * Subscribers have event handling methods that must be annotated by {@link Subscribe}.
     * The {@link Subscribe} annotation also allows configuration like {@link
     * ThreadMode} and priority.
     */
    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        // 查詢註冊方法的核心
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // 維護訂閱關係的核心代碼
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

獲取訂閱信息 (構建 SubscriberMethod

註解方式獲取

從下面的代碼能夠看出,獲取註解方法的流程是:github

  • 獲取類的全部方法
  • 只有一個參數的方法,判斷是否有 @subscribe註解
  • 其餘都過濾(視狀況是否拋異常出來)
private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

對比Guava的獲取註解方法, 實現的主要區別是Guava多了一個獲取超類的過程安全

- Guava獲取全部的超類, 根據每一個類的 `getDeclaredMethods` 獲取全部的方法,而後判斷是否有註解
- Greenrobot 則是直接調用類的  `getDeclaredMethods` 獲取全部方法, 而後判斷是否有註解

上面兩個有什麼區別 ?ide

class.getDeclaredMethods 能夠獲取類全部申明的方法,也就是說 private, protected, 默認,public四個做用域的均可以獲取到,換句話說,Guava的訂閱者方法能夠是私有的!!!,即使父類的私有方法也是能夠的, static也無所謂post

Greenrobot 中限制了方法的做用域共有的非靜態方法,有且只有一個參數,並且只是對當前類而言學習

非註解方式

支持非註解方式進行註冊,主要藉助SubscriberInfoIndex 來指定註冊方法 。咱們能夠倒推一下這個設計思路:測試

  • 註冊,首先是要肯定將類的哪些方法註冊到 EventBus
  • 排除掉註解方式;還有一種常見的就是咱們定義一種方式,能夠將咱們須要註冊的方法直接返回
  • 定義一個接口,用於返回註冊類的全部訂閱信息,而後把這個接口也丟給 EventBus

下面貼一個非註解方式的測試用例,方便理解, SubscriberInfoIndex就是咱們定義用於返回全部的訂閱關係的接口,經過調用 EventBus.addIndex(非線程安全)方法告知eventbus對象ui

public class EventBusIndexTest {
    private String value;

    @Test
    /** Ensures the index is actually used and no reflection fall-back kicks in. */
    public void testManualIndexWithoutAnnotation() {
        SubscriberInfoIndex index = new SubscriberInfoIndex() {

            @Override
            public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
                Assert.assertEquals(EventBusIndexTest.class, subscriberClass);
                SubscriberMethodInfo[] methodInfos = {
                        new SubscriberMethodInfo("someMethodWithoutAnnotation", String.class)
                };
                return new SimpleSubscriberInfo(EventBusIndexTest.class, false, methodInfos);
            }
        };

        EventBus eventBus = EventBus.builder().addIndex(index).build();
        eventBus.register(this);
        eventBus.post("Yepp");
        eventBus.unregister(this);
        Assert.assertEquals("Yepp", value);
    }

    public void someMethodWithoutAnnotation(String value) {
        this.value = value;
    }
}

下面則開始進入正式的代碼緯度分析this

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState); // 這裏是獲取訂閱方法相關信息的核心
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                findUsingReflectionInSingleClass(findState); // 這裏作了兼容, 以註解方式去掃描獲取訂閱方法
            }
            // 再去掃父類的訂閱信息
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

核心代碼以下, 有意思的一點就是面對繼承關係的處理,究竟是選擇子類的訂閱關係仍是父類的訂閱關係

private SubscriberInfo getSubscriberInfo(FindState findState) {
    // 下面的判斷邏輯主要針對子類繼承了父類中的訂閱方法時, 返回子類的訂閱信息
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

維護註冊關係

經過上面能夠將訂閱者全部註冊方法找出來,找出來以後固然是要存起來,也就是這一小節的內容,如何存,存了以後如何用 private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; 這個東西保存的就是 Event -> 訂閱方法的映射關係, 至關於Guava的 SubscriberRegistry

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //////////////////////////
        ////// 開始將訂閱信息塞入 subscriptionsByEventType 時間-註冊關係映射表中
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        
        // 不存再, 則塞空; 已存在, 則拋異常
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            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;
            }
        }

        // typesBySubscriber 保存訂閱對象,和訂閱對象監聽的全部事件類的映射關係, 下面的邏輯就是保證這個映射關係的完整性
        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>).
                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);
            }
        }
    }

##2. 取消註冊

這個沒什麼好說的,就是上面的逆過程

3. 小結

上面分析了註冊的流程,基本上思路和 Guava 的沒什麼大的區別,不一樣的是,作了更多的東西,實現了更多的功能,其中咱們能夠參考的幾個設計思路

  1. 經典單例模式的 defaultInstance, 這個在jdk裏面用得很是多,好比 Boolean.True, Collections.EMPTY_LIST,會給一些默認的實例,不用每次都去建立,上面的實現其實有不一樣的,實際對比以後比較明顯能夠感知
  2. 註解方式註冊
    • 如何獲取某註解的方法,這個使用和guava的有些區別,特別是對超類以及做用域的狀況處理
  3. 非註解方式註冊
    • 典型的借用輔助類(SubscriberInfoIndex)來完成預期目標

此外實現的細節上也能夠看看,參考優秀的寫法才能提升本身的書寫質量,固然每一個人的習慣都不同,就好比對EventBus類中的某些用法,本人持保留意見

大量使用內部類,且屬性基本上都直接訪問,不經過Getter/Setter方法 (講道理,對這種方式仍是不太習慣)
EventBus中大量的配置屬性,我的傾向使用Option配置類來作,會使類結構更加清晰
相關文章
相關標籤/搜索