訂閱者的註冊 + 消息推送java
先貼出註冊代碼, 能夠可到和 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. 取消註冊
這個沒什麼好說的,就是上面的逆過程
上面分析了註冊的流程,基本上思路和 Guava 的沒什麼大的區別,不一樣的是,作了更多的東西,實現了更多的功能,其中咱們能夠參考的幾個設計思路
defaultInstance
, 這個在jdk裏面用得很是多,好比 Boolean.True
, Collections.EMPTY_LIST
,會給一些默認的實例,不用每次都去建立,上面的實現其實有不一樣的,實際對比以後比較明顯能夠感知SubscriberInfoIndex
)來完成預期目標此外實現的細節上也能夠看看,參考優秀的寫法才能提升本身的書寫質量,固然每一個人的習慣都不同,就好比對EventBus類中的某些用法,本人持保留意見
大量使用內部類,且屬性基本上都直接訪問,不經過Getter/Setter方法 (講道理,對這種方式仍是不太習慣) EventBus中大量的配置屬性,我的傾向使用Option配置類來作,會使類結構更加清晰