輕量級Android事件派發框架——EventBus源碼全解析

Hello,All,我是來自58同城的一名Android開發工程師,在58集團從事Android SDK的開發工作。

關注我,即刻解鎖全部乾貨推文

PS:關注,私信我,幫你內推58,常年招聘前端,移動端,後端,算法。

也歡迎關注我的公衆號,在這裏可以找到我,同時,這裏會不定期地推送一些時下最熱門的技術文章和互聯網行業工作心路歷程

 


 

12k視覺-客車品牌後期修圖部分集錦_攝...

 

說到當今Android開發領域,最火的事件派發框架,EventBus絕對是黑科技滿滿,C位出道。

在經歷了歷史上2個比較大的版本迭代後,它以其輕巧、簡單、無侵入性的使用方式,吸引了越來越多的移動端開發者,並將其囊括進了他們的APP開發框架當中。

正好趕在清明假期,抽時間來研究了一下框架的源碼,順帶也熟悉了一下Annotation註解的使用方式。

不得不說,註解對於Java所帶來的優勢太大了。註解,可以簡單的理解爲描述數據的數據,在這樣的理論基礎之上,通過對外暴露的註解接口,我們在內部處理經過註解後的方法,使其達到一定的數據配置和一些自動化處理過程。並且,我曾經考慮過,是否可以將其引入到正在維護的公司代碼中呢?

正好前段時間打算把公司代碼中的部分業務邏輯進行優化,涉及到了事件傳遞部分,自然就想到了EventBus,通過閱讀EventBus源碼,我也再次領略到了其設計的精巧和Annotation給我們帶來解耦之方便和其強大之處。

接下來,就讓我們走進源碼,來看看EventBus爲何如此之精巧,如此之強大吧!

以下內容基於EventBus 3.2.0版本進行解析。

源碼講解部分總共分三個部分進行,與我們正常的開發和使用順序一致:

 

  1. EventBus.java類的結構分析
  2. EventBus訂閱註冊過程
  3. 事件派發過程
  4. EventBus取消訂閱過程

 

  1. Eventbus.java的結構分析
EventBus(EventBusBuilder builder) {
    logger = builder.getLogger();
    subscriptionsByEventType = new HashMap<>();
    typesBySubscriber = new HashMap<>();
    stickyEvents = new ConcurrentHashMap<>();
    mainThreadSupport = builder.getMainThreadSupport();
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    executorService = builder.executorService;
}

通過EventBus的構造函數我們可以很容易地看出它的結構構成,可以看到,在他的構造函數裏,採用了EventBusBuilder攜帶參數的方式,將前置的配置參數進行了傳遞,當然,在默認情況下,EventBus已經幫我們配置好了,不需要我們主動調用默認的構造函數。

而在默認的EventBus實例的獲取過程中,也是採用了單例的設計模式,並且,這裏還貼心地採用了線程安全的雙重檢查單例模式,不吹不黑,在這塊考慮的還是挺周到的。

 

2.EventBus訂閱註冊過程:

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

讓我們從register函數開始。

當在我們的代碼中調用了register函數後,在函數內部,首先是獲取到傳進來的subscriber的Class對象,接着,將這個Class對象通過一個SubscriberMethodsFinder對象的findSubScriberMethods方法,來找到加了@Subscribe標籤的方法,並將其轉化爲了一個Java對象,這個轉化的過程和我們正常的註解解析過程一致。如果對註解的運行原理不懂的同學,可以關注公衆號並回復 anno ,有一篇純正的專家級Annotation使用及原理講解發給你,相信看過之後一定會幫助你在註解使用和原理分析這塊獲得暴力提升

好,讓我們來看看findSubScriberMethods裏面都做了些什麼:

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

else {
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

首先,我們可以看到,先是做了一個判重的檢查,如果訂閱的這個類之前已經參與過訂閱,那麼就不回再次添加進監聽隊列了,然後,通過訂閱類來尋找到了加註Subscriber標籤的方法,在這裏,經過我實測,調用的是第一個方法:findUsingReflection(subscribeClass)方法,在這個方法裏,內部調用了一個findUsingReflectionInSingleClass(findState)

這樣一個方法,這個看似平淡無奇,但作者也將其性能做到了極致,通過註釋,我們能夠看到,作者在獲取註冊類的註解方法時,是通過findState.clazz.getDeclaredMethods();

這樣的方式來執行的,並且簡單粗暴地解釋了這麼做的原因:

// This is faster than getMethods, especially when subscribers are fat classes like Activities

在這之後,就將這個訂閱方法的集合作爲一個對象進行了返回,返回到了我們的register方法裏。

此時,resiger已經拿到了subscriber類的訂閱方法集合,接下來,就是把這些集合進行統一的存儲,管理,這個動作是由subscribe方法來做的


// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    。。。
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    }
。。。
    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;
        }
    }
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    if (subscriberMethod.sticky) {
        if (eventInheritance) {
               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);
        }
    }
}

可以看到,這裏是將註冊進來的事件都通過subscriptionsByEventType來進行了管理,並且這裏使用了CopyOnWriteArrayList容器,好,讓我們記住這個貫穿於EventBus始終的對象“subscriptionsByEventType”。

 

你以爲這就完了?No No,更精彩的還在後面,還記得stickyEvent嗎?沒錯,就是每次面試都會問起來的黏性事件,好像很神奇是吧?爲什麼在一個沒有啓動起來的註冊接收者裏面仍然能夠接收到呢?

讓我們把目光聚焦到這裏:

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

可以看到,這一塊是在subscribe中專門用來處理黏性事件的邏輯。

如果檢測到當前註冊的方法是黏性事件方法,就走到這塊的處理邏輯,最終是通過

checkPostStickyEventToSubscription(newSubscription, stickyEvent);並且在內部調用postToSubscription(newSubscription, stickyEvent, isMainThread());來進行事件派發的

 

3.事件派發過程

事件派發過程讓我們從post方法開始。

/** Posts the given event to the event bus. */
public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);
    if (!postingState.isPosting) {
        postingState.isMainThread = isMainThread();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

這裏我們能夠看到,在函數開始的地方先是通過一個currentPostingThreadState來get了一個當先線程對象的post狀態變量
那麼,這個currentPostingThreadState是什麼呢,讓我們到定義處去看一看

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};

可以看到,這裏使用了ThreadLocal,ThreadLocal???是否有一種似曾相識的感覺呢?

沒錯,在我們的Handler機制當中也使用了它!

ThreadLocal並不是一個Thread,而是用來存儲每個Thread模型中變量的,而且是和其他線程隔離的,只有本線程內能夠訪問到。

所以,這裏get過來的PostingState變量就是屬於當前線程的。

接着,我們看到,通過調用eventQueue來獲取到了一個時間隊列,也就是當前線程的事件隊列他主要是用來進行事件派發的,我們可以看到,在方法的第三行,就是將剛剛傳進來的時間模型給添加進了eventQueue裏面。

接着,就來到了真正的事件派發過程postSingleEvent(eventQueue.remove(0), postingState);

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

在這裏,先是根據eventType找到了對應的加了subscribe標籤的class,然後,在內部調用了postToSubscription(subscription, event, postingState.isMainThread);開始了真正的派發


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

這個方法裏使用了switch語句根據註冊時的ThreadMode進行不同的處理,這裏先對幾種不同的threadMode進行一個解析:

通過ThreadMode枚舉類,我們可以看到, EventBus 3.2.0一共有5種模式:

POSTING:派發線程和訂閱線程在同一個線程模式。同時也是默認的派發模式。

MAIN:在主線程進行派發,同時也是具有Android特色的派發方式,因爲在Java中只有 Android具有主線程模式。

MAIN_ORDERED:具有順序的主線程派發模式。

BACKGROUND:後臺派發模式。如果當前的派發線程在非主線程,則直接採用當前線程進行派發。如果當前線程在主線程,則進行一次線程切換,單獨開一個線程進行子線程派發。

ASYNC:異步派發模式。不論當前是在主線程還是在子線程,總是會單獨開闢一個線程進行事件的派發,但是不用擔心,EventBus會幫你使用線程池來優化線程使用體驗,這一點,在註釋裏面也是明確註明了的。

 

值得注意的是:

在POSTING這個case中,使用了反射的方式直接通過method調用了註冊方法,並且調用的線程和post的線程也是一致的。

而在MAIN 這個case中,也是Eventbus的一大精彩看點:主子線程的切換。

case MAIN:
    if (isMainThread) {
        invokeSubscriber(subscription, event);
    } else {
        mainThreadPoster.enqueue(subscription, event);
    }
    break;

我們可以看到,在這個case中先是通過傳入的標誌位來判斷是否當前在主線程,如果在主線程則直接invoke註冊方法。如果在非主線程,則通過HandlerPoster的enqueue方法將當前的事件進行入隊。

public void enqueue(Subscription subscription, Object event) {
    PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
    synchronized (this) {
        queue.enqueue(pendingPost);
        if (!handlerActive) {
            handlerActive = true;
            if (!sendMessage(obtainMessage())) {
                throw new EventBusException("Could not send handler message");
            }
        }
    }
}

在HandlerPoster的enqueue方法中,EventBus的團隊爲了極致的性能體驗,並沒有採用JAVA SDK 中的LinkedList等鏈表容器,而是手動的實現了名爲queue的PendingPostQueue單向鏈表。

接下來,我們可以看到,通過sendMessage發起了一次Handler消息傳遞流程,並且使用了obtainMessage來獲取到了一個空的Message對象來發送出去。

到了handleMessage之後,此時已經切換爲了主線程處理模式,這裏依舊是使用了eventbus的invokeSubscriber方法來反射式地進行了註冊方法的調用。

看到這裏,相信你一定有個疑問,到底是如何切換到主線程的Handler的?

讓我們切換到HandlerPoster對象的構造方法位置,來看看是誰調用了他。

/**
 * Interface to the "main" thread, which can be whatever you like. Typically on Android, Android's main thread is used.
 */
public interface MainThreadSupport {
    boolean isMainThread();
    Poster createPoster(EventBus eventBus);
    class AndroidHandlerMainThreadSupport implements MainThreadSupport {
        private final Looper looper;
        public AndroidHandlerMainThreadSupport(Looper looper) {
            this.looper = looper;
        }
        @Override
        public boolean isMainThread() {
            return looper == Looper.myLooper();
        }
        @Override
        public Poster createPoster(EventBus eventBus) {
            return new HandlerPoster(eventBus, looper, 10);
        }
    }

}

可以看到,在這裏使用了一個接口的內部類AndroidHandlerMainThreadSupport來進行了主線程的切換動作,並且在構造方法當中還傳入了一個looper對象。

那麼問題來了,這個looper是何方神聖?

通過FindUsage我們繼續向上尋找,發現其實是在這裏:

MainThreadSupport getMainThreadSupport() {
    if (mainThreadSupport != null) {
        return mainThreadSupport;
    } else if (AndroidLogger.isAndroidLogAvailable()) {
        Object looperOrNull = getAndroidMainLooperOrNull();
        return looperOrNull == null ? null :
                new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
    } else {
        return null;
    }
}

在這裏get了一個MainLooper,並且向下傳遞,最後,HandlerPoster也就獲取到了主線程的looper,並且經過他構造的Handler也自然就成爲了主線程的Handler,多麼精巧的設計!

 

4.EventBus解註冊過程

在經過了註冊,使用之後,我們還要記得把註冊對象進行釋放,也就是我們的解註冊過程,這個過程相對就容易很多了,相當於是註冊過程的逆過程,這裏就不再進行詳述了。

 

 

 

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