1. 程式人生 > >EventBus 及一些思考

EventBus 及一些思考

EventBus 是 Android 開發的一種常用框架,其解耦的思維令人讚歎

從特性上來講,其與 Android SDK中的BroadcastReceiver很像,二者都是註冊,傳送事件,反註冊,都可以定義事件的優先順序,且都支援粘性(sticky)事件,只是EventBus 使用起來簡單得多,而且不能跨程序

Android SDK其實也有一個不能跨程序的BroadcastReceiver機制——LocalBroadcastManager,其傳送和接受的廣播只能在本程序,相比傳統的 registerBroadcastReceiver,其有著更高的安全性,與EventBus的相似度也更高

關於 EventBus 基礎,請參考:

EventBus

使用教程 高階用法 原始碼解析

這裡結合原始碼,記錄幾個在 EventBus 學習和使用中值得思考和注意的地方:

註冊

  1. 哪些方法會被註冊 ?
  2. 多次註冊同一物件會如何?
  3. 註冊物件沒有被 Subscribe 註解的方法會如何 ?
  4. 註冊物件時父類中被 Subscribe 註解的方法會被註冊嗎?
  5. 如果 子類B 重寫了 父類A 的方法 fun,註冊子類B 的物件會發生什麼?呼叫時會呼叫哪個類的方法?

上述問題,我們需要分析 register方法:

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    // 1. 找 subscriber 中被需要被註冊的方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        // 2. 註冊這些方法
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

先來看上面程式碼第一點;

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    // 快取中取
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    if (ignoreGeneratedIndex) {
        // 通過反射去找
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        // 通過 Subscriber Index 去找,這裡如果沒找到,也會通過反射去找
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    // 如果沒有 找到被 Subscribe 註解的方法,丟擲異常  ———— 問題 3
    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;
    }
}

// 繼續分析註冊方法的查詢過程findUsingReflection
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        findUsingReflectionInSingleClass(findState);
        // 繼續去 subscriberClass 的父類中找,但是這裡有異常情況(幾乎不會發生),往下看 ———— 問題 4
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}
// findState.moveToSuperclass();
void moveToSuperclass() {
    if (skipSuperClasses) { // 異常情況,至於在哪裡設定該標誌位呢,繼續往下看
        clazz = null;
    }
    // ...
}

// 繼續分析註冊方法的查詢過程 findUsingReflectionInSingleClass
private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    // 先通過getDeclaredMethods查詢,再通過getMethods查詢
    try {
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        methods = findState.clazz.getMethods();
        // ...
        // 問題 4 的異常情況,在 getDeclaredMethods 發生異常時,跳過父類的註冊方法查詢
        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)) {
                        // 方法被註冊的條件: public,被 Subscribe 註解,引數列表只有1個引數(也就是事件型別) ———— 問題 1
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                // 在設定了 strictMethodVerification 時,如果 Subscribe 註解的方法引數個數不是 1,丟擲異常,預設 strictMethodVerification標誌是false,可以通過 EventBusBuilder 設定
                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)) {
            // 在設定了 strictMethodVerification 時,如果被 Subscribe 註解的方法不是 public,丟擲異常
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

再來看 register方法中的第2 點:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    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 {
        // 已經註冊過了該 subscriber,再次註冊丟擲異常  ———— 問題 2
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }
    // ...
}

上述問題 5 ,程式碼中沒寫,但是其實在回答問題 4 時已經回答了,在尋找註冊方法時,會註冊父類的合格的方法,那麼在post 呼叫時,呼叫的是哪個方法呢,EventBus 通過反射呼叫方法,自然也就是實現類的方法;

事件觸發

  1. post時,如果沒有找到 eventType 對應的註冊方法會如何?
  2. 事件的 eventType 呼叫時符合多型嗎?
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    // 設定了 eventInheritance 標誌,查詢 eventType 的 父類 和 父介面 的對應的事件型別,eventInheritance 標誌預設為 true  ———— 問題 2
    if (eventInheritance) {
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    if (!subscriptionFound) {
        // 沒有找到對應 eventType 的註冊方法,先打個日誌
        if (logNoSubscriberMessages) {
            logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
        }
        // 如果設定了 sendNoSubscriberEvent 標誌,post 一個 NoSubscriberEvent 事件  ———— 問題 1
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

反註冊

  1. 多次反註冊同一物件,或者反註冊一個未被註冊過的物件會如何?
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 {
        // 沒找到,就打了個日誌  ———— 問題 1
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}