EventBus 及一些思考
阿新 • • 發佈:2020-03-01
EventBus 是 Android 開發的一種常用框架,其解耦的思維令人讚歎
從特性上來講,其與 Android SDK中的BroadcastReceiver很像,二者都是註冊,傳送事件,反註冊,都可以定義事件的優先順序,且都支援粘性(sticky)事件,只是EventBus 使用起來簡單得多,而且不能跨程序
Android SDK其實也有一個不能跨程序的BroadcastReceiver機制——LocalBroadcastManager,其傳送和接受的廣播只能在本程序,相比傳統的 registerBroadcastReceiver,其有著更高的安全性,與EventBus的相似度也更高
關於 EventBus 基礎,請參考:
EventBus
使用教程 高階用法 原始碼解析
這裡結合原始碼,記錄幾個在 EventBus 學習和使用中值得思考和注意的地方:
註冊
- 哪些方法會被註冊 ?
- 多次註冊同一物件會如何?
- 註冊物件沒有被 Subscribe 註解的方法會如何 ?
- 註冊物件時父類中被 Subscribe 註解的方法會被註冊嗎?
- 如果 子類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 通過反射呼叫方法,自然也就是實現類的方法;
事件觸發
- post時,如果沒有找到 eventType 對應的註冊方法會如何?
- 事件的 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));
}
}
}
反註冊
- 多次反註冊同一物件,或者反註冊一個未被註冊過的物件會如何?
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());
}
}