EventBus 3.0 略解

記錄的EventBus架構中用到的技巧

大體的架構——事件總線和觀察者模式,BusEvent中所有事件發放和訂閱都是在一個單例中去實現的,最基礎的代碼結構如下,EventBus在這個基礎上去優化的

public class EventCenter {

    private static EventCenter instance;
    private static final Object lock = new Object();

    public static EventCenter getDefault() {
        if (instance == null) {
            synchronized (lock) {
                if (instance == null) {
                    instance = new EventCenter();
                }
            }
        }
        return instance;
    }

    private ArrayList<EventListen> listens = new ArrayList<>();

    public synchronized void addListen(EventListen listen) {
        listens.add(listen);
    }

    public synchronized void removeListen(EventListen listen) {
        listens.remove(listen);
    }

    public synchronized void post(int eventId, HashMap<String, Object> param) {
        for (EventListen listen : listens) {
            listen.handle(eventId, param);
        }
    }

    public interface EventListen {
        public void handle(int eventId, HashMap<String, Object> param);
    }
}

1 首先EventBus是一個單例

/** Convenience singleton for apps using a process-wide EventBus instance. */
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

2 訂閱,EventBus的訂閱是一個對象爲訂閱單位的,有效訂閱方法僅爲該類和父類使用@Subsrcibe標記的方法。比如,該對象中一個對象變量中使用@Subsrcibe標記的方法是沒有加到訂閱中的。接下來通過看整個訂閱過程

傳入要訂閱的對象

EventBus.getDefault().register(this);

找出訂閱的方法,具體分爲兩種。一種是通過反射找出@Subsrcibe標記的方法,一種是通過APT運行時編譯的方法,先在編譯代碼時,就將@Subsrcibe標記的方法添加到集合中,在註冊時快速找出@Subsrcibe標記的方法,省去使用反射找方法這一步,增加效率(這個技術在EventBus 3.0後加入新特性Subsrcibe Index

關鍵代碼:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        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;
        }
    }

ignoreGeneratedIndex這個變量時構建默認設置的,是否忽略新特性Subscribe Index,爲false,不忽略

在這裏添加緩存,避免多次重複查找(在EventBus中緩存和複用的技術用到非常非常多,FindState就是一直在複用的,避免多次重複新建對象)

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            // 如果不使用新特性,即爲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.clazz 置null,停止
            // 查找,退出循環
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

我們看使用反射查找那部分關鍵的代碼

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()));
                        }
                    }
}

符合要求的方法,必須是public修飾的,而且有且僅有一個參數,爲什麼只能有一個方法,這是因爲EventBus管理訂閱的方式是通過參數區分管理,即同一個參數類型就放在同一個集合裏,這樣當發出這個參數的事件時,只需將這個集合遍歷發放一遍即可。這樣無關的訂閱就不會接收到事件。

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 {
            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;
            }
        }

到這裏基本完成了訂閱步驟,訂閱步驟中關於集合部分的代碼都是在同步代碼塊中的,說明EventBus支持異步的。

補充一點:黏性事件的實現也是在訂閱步驟中的

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);
            }
        }

其實就是在黏性事件放在一個集合中,在註冊時,遍歷一遍,看是否有符合條件的黏性事件。

3 發送事件 主要技術點在訂閱方法指定的線程內處理事件

下發訂閱事件

EventBus.getDefault().post("");

在這裏使用到了ThreadLocal用於獲得當前下放訂閱事件的線程的狀態

   final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<>();  // 當前線程未下發的信息
        boolean isPosting;     // 這個變量用於高優先級攔截事件時使用
        boolean isMainThread;   // 是否爲主線程
        Subscription subscription;   // 當前正處理下發事件的訂閱方法
        Object event;     // 下發的訂閱事件
        boolean canceled;   // 該線程取消下放事件
    }

下放的這個事件是隻能是單個對象,通過預先配置eventInheritance變量,爲true,使用繼承關係。例如下放訂閱對象是A繼承B,如果使用使用繼承關係會同時下發到監聽A的方法和監聽B的方法。如不使用繼承關係只會下發到監聽A的方法。

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        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);
        }
    // ..
        }
    }

然後通過優先級下發都每一個訂閱的方法

 private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            // 獲得訂閱下放訂閱事件的方法集合
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        // 按照優先級下發
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    postToSubscription(subscription, event, postingState.isMainThread);
                    // 高優先級可以停止繼續下發事件
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    break;
                }
            }
            return true;
        }
        return false;
    }

注意這個取消繼續下發,個人感覺由於線程問題,使用時需要十分小心

接下就是下發訂閱事件的核心代碼了,按照訂閱方法指定的線程下發訂閱事件

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

(1)POSTING 

和下發訂閱事件同一條線程,直接在下發事件線程通過反射處理事件

 void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

(2)MAIN 

在主線程下發事件,如果不在主線程,通過主線程的hander下發事件。

(3)MAIN_ORDEREN

也是在主線程下發事件與MAIN不同,直接往主線程的hander中丟,如果持有主線程未null在下發線程處理,這裏作者說了有缺陷可能之後會優化

(4)BACKGROUND

如果下發線程是主線程就丟到線程池中去處理,就在不要在主線程中處理。

(5)ASYNC

不管三七二十一直接丟到線程中去處理,仔細的話就會發現BackgroundPoster和AsyncPoster這裏兩個類的實現其實是一樣的。

還有黏性下發事件和下發事件只是多了一步

 public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }

4 記得要取消訂閱,避免內存泄漏,EventBus用的全是強引用,代碼只是和訂閱反着來

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 {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

到這裏EventBus架構源碼解析就基本全了,源碼並不多,用的想法、技術點和數據結構都挺精妙的。

裏面就用到了隊列的數據結構,可以學習一下

public class Queue {
    private Node head;
    private Node tail;

    synchronized void enqueue(Node o) {
        if (tail != null) {
            tail.next = o;
            tail = o;
        } else if (head == null) {
            head = tail = o;
        } else {
            throw new IllegalStateException("Head present, but no tail");
        }
    }

    synchronized Node poll() {
        Node node = head;
        if (head != null) {
            head = head.next;
            if (head == null) {
                tail = null;
            }
        }
        return node;
    }

    public class Node {
        public Node next;
        public Object o;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章