100行程式碼拆解EventBus核心邏輯(一)

  • 2019 年 11 月 23 日
  • 筆記

Github:https://github.com/hylinux1024 微信公眾號:終身開發者(angrycode)

EventBus 作為一個基礎的消息傳遞組件,了解其核心實現原理是日常開發工作之外需要做的必修課。本系列希望通過自己實現一個類似的消息傳遞組件 EasyBus 來理解 EventBus 的核心實現原理。

從官方的原理圖可以直觀的看出 EventBus 是一個基於訂閱發布的消息傳遞組件。訂閱者通過 EventBus 進行註冊,登記自己感興趣的事件 Event,發布者將 Event 發送到 EventBus 後就將會查詢監聽事件 Event 的訂閱者,然後將事件 Event 轉發到訂閱者中。

可以發現其實就是觀察者模式的實現。

觀察者模式中,被觀察者( Subject)內部會通過 List 來存儲觀察者的實例,然後就通過 notify() 方法可以通知觀察者。 在 EventBus 庫中,定義了 onEventXXXX(MessageEvent) 方法的類就是觀察者,而 EventBus 自己就是被觀察者。

接下來實現一個簡易版本的消息傳遞 EasyBus 。

首先預覽下整體的類結構

com/github/easybus├── EasyBus.java├── Logger.java├── SubscriberMethod.java├── Subscription.java└── demo    ├── MainActivity.kt    └── MessageEvent.java

參考 EventBus 定義了一個 EasyBus 類,這個將是我們使用 EasyBus 的主要入口。

單例方法

public class EasyBus {    ...    private EasyBus() {        eventTypeBySubscriber = new HashMap<>();        subscriptionsByEventType = new HashMap<>();    }      /**     * 使用靜態內部類的方式實現單例     */    private static class Holder {        static EasyBus instance = new EasyBus();    }      public static EasyBus getInstance() {        return Holder.instance;    }    ...}

在 EasyBus 中使用靜態內部類的方式實現單例,這樣可以保證在一個進程內只有一個 EasyBus 的實例。 這裡有兩個變數需要重點解釋一下:

    /**     * 通過 EventType 查詢訂閱者資訊(即註冊EasyBus的Class類以及回調的通知方法)     * 當調用post方法的時候,可以通過Event類查詢到對應的訂閱者資訊     * 從而快速的執行通知介面     */    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;    /**     * 通過訂閱者(即註冊 EasyBus 的類實例)查詢事件 EventType Class     * 事件類型即為 onEvent 方法中的參數類型     */    private final Map<Object, List<Class<?>>> eventTypeBySubscriber;

這兩個變數都是 Map 類型。subscriptionsByEventType 的 key 是 Event 類的類型, value 則訂閱者的列表資訊,通過 Subscription 類來封裝;而 eventTypeBySubscriber 的 key 是訂閱者實例, value 是 Event 類的類型。

回憶上面的觀察者模式類圖,被觀察者需要有一個 List 用來存儲觀察者列表,這裡的觀察者列表就是通過上面兩個 Map 變數來實現的。

register

public void register(Object subscriber) {        Class<?> subscriberClass = subscriber.getClass();        Method[] methods = subscriberClass.getDeclaredMethods();        List<SubscriberMethod> subscriberMethods = new ArrayList<>();        for (Method method : methods) {            Class<?>[] parameterTypes = method.getParameterTypes();            if (parameterTypes.length != 1) {                continue;            }            if (method.getName().startsWith("onEvent")) {                subscriberMethods.add(new SubscriberMethod(method, parameterTypes[0]));            }        }        synchronized (this) {            for (SubscriberMethod method : subscriberMethods) {                subscribe(subscriber, method);            }        }    }

註冊方法的作用是解析觀察者的方法和事件類型,然後將觀察者資訊添加到 被觀察者中的 List 里。實現邏輯也很簡單,通過 Class 類進行反射調用類中的方法,過濾參數個數為1,且方法名稱是以 onEvent* 開頭的方法,就把其添加到 subscriberMethods 列表中。其中 SubscriberMethod 是訂閱者的接收通知的方法(可以對比觀察者模式中的 update 方法),它封裝了方法名稱、和方法中的參數類型(即事件類型)。

接下來就調用 subscribe 方法實現訂閱邏輯。

private void subscribe(Object subscriber, SubscriberMethod method) {        Logger.i(subscriber.getClass().getSimpleName() + ":" + method.method.getName());        // 查詢這個事件類型中對應的訂閱者資訊(即接收相同事件的註冊者的方法有哪些)        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(method.eventType);        if (subscriptions == null) {            subscriptions = new CopyOnWriteArrayList<>();        }        subscriptions.add(new Subscription(subscriber, method));        subscriptionsByEventType.put(method.eventType, subscriptions);        // 查詢註冊者中的接收的事件 Event 類型(即 onEvent 方法中的參數類型)        List<Class<?>> eventTypes = eventTypeBySubscriber.get(subscriber);        if (eventTypes == null) {              = new CopyOnWriteArrayList<>();        }        eventTypes.add(method.eventType);          eventTypeBySubscriber.put(subscriber, eventTypes);    }

該方法的主要作用就是將上面解析到的方法和事件類型添加到 subscriptionsByEventType 和 eventTypeBySubscriber 這兩個 Map 中。 首先查詢 subscriptionsByEventType 是否有對應的訂閱資訊,如果沒有則初始化列表 subscriptions 。然後將訂閱者實例( subscriber)與訂閱者中的方法( method)封裝成 Subscription 。最後添加到 subscriptions 中。 同樣地查詢 eventTypeBySubscriber 中是否有對應的事件類型,沒有則初始化列表 eventTypes。之後將 eventTypes 存儲在 eventTypeBySubscriber 這個 Map 里。

post

public void post(Object event) {          List<Subscription> subscriptionList = subscriptionsByEventType.get(event.getClass());        if (subscriptionList == null) {            Logger.i("not found subscriber");            return;        }        for (Subscription subscription : subscriptionList) {            subscription.notifySubscriber(event);        }    }

post 邏輯也很簡單,主要通過事件類型查詢對應的訂閱者資訊 List ,然後遍歷該列表進行消息事件通知。

訂閱者資訊類主要封裝訂閱者實例資訊和對應的方法資訊

final class Subscription {    final Object subscriber;    final SubscriberMethod subscriberMethod;      public Subscription(Object subscriber, SubscriberMethod subscriberMethod) {        this.subscriber = subscriber;        this.subscriberMethod = subscriberMethod;    }      public void notifySubscriber(Object event) {        try {            subscriberMethod.method.invoke(subscriber, event);        } catch (Exception e) {            e.printStackTrace();        }    }    ...}

Subscription 中還有一個重要的方法 notifySubscriber , 它是通過反射對訂閱者的方法進行調用。

unregister

梳理了上面的 register 方法 unregister 方法就比較容易理解了。

public void unregister(Object subscriber) {        List<Class<?>> eventTypes = eventTypeBySubscriber.get(subscriber);        if (eventTypes != null) {            for (Class<?> eventType : eventTypes) {                unsubscribe(subscriber, eventType);            }        }    }

通過訂閱者實例資訊從 eventTypeBySubscriber 查詢到訂閱的事件類型列表,然後遍歷 eventTypes 執行 unsubscribe 方法。

private void unsubscribe(Object subscriber, Class<?> eventType) {        List<Subscription> subscriptionList = subscriptionsByEventType.get(eventType);        if (subscriptionList == null) {            return;        }        int size = subscriptionList.size();        for (int i = 0; i < size; i++) {            if (subscriptionList.get(i).subscriber == subscriber) {                subscriptionList.remove(i);                i--;                size--;            }        }    }

在 unsubscribe 方法中將訂閱者資訊從訂閱者列表中移除,就完成了退訂功能。

引用

  • https://github.com/greenrobot/EventBus
  • https://github.com/hylinux1024/EasyBus

Tags: