EventBus設計與實現(xiàn)分析——特性介紹


EventBus是一個 發(fā)布/訂閱 模式的消息總線庫,它簡化了應用程序內各組件間、組件與后臺線程間的通信,解耦了事件的發(fā)送者和接收者,避免了復雜的、易于出錯的依賴及生命周期問題,可以使我們的代碼更加簡潔、健壯。

在不使用EventBus的情況下,我們也可能會使用諸如 Observable/Observer 這樣得一些機制來處理事件的監(jiān)聽/發(fā)布。如果在我們的應用程序中,有許多地方需要使用事件的監(jiān)聽/發(fā)布,則我們應用程序的整個結構可能就會像下面這個樣子:


每一處需要用到事件的監(jiān)聽/發(fā)布的地方,都需要實現(xiàn)一整套監(jiān)聽/發(fā)布的機制,比如定義Listener接口,定義Notification Center/Observable,定義事件類,定義注冊監(jiān)聽者的方法、移除監(jiān)聽者的方法、發(fā)布事件的方法等。我們不得不寫許多繁瑣的,甚至常常是重復的冗余的代碼來實現(xiàn)我們的設計目的。

引入EventBus庫之后,事件的監(jiān)聽/發(fā)布將變得非常簡單,我們應用程序的結構也將更加簡潔,會如下面這樣:


我們可以將事件監(jiān)聽者的管理,注冊監(jiān)聽者、移除監(jiān)聽者,事件發(fā)布等方法等都交給EventBus來完成,而只定義事件類,實現(xiàn)事件處理方法即可。

在使用Observable/Observer來實現(xiàn)事件的監(jiān)聽/發(fā)布時,監(jiān)聽者和事件發(fā)布者之間的關聯(lián)關系非常明晰,事件發(fā)布者找到事件的監(jiān)聽者從不成為問題。引入EventBus之后,它會統(tǒng)一管理所有對不同類型事件感興趣的監(jiān)聽者,則事件發(fā)布者在發(fā)布事件時,能夠準確高效地找到對事件感興趣的監(jiān)聽者將成為重要的問題。后面我們就通過對EventBus代碼實現(xiàn)的分析,來獲取這樣一些重要問題的答案,同時來欣賞EventBus得設計。

EventBus的基本使用

在具體分析EventBus的代碼之前,我們先來了解一下EventBus的用法。想要使用EventBus,首先需要在我們應用程序的Gradle腳本中添加對于EventBus的依賴,以當前最新版3.0.0為例:

compile 'org.greenrobot:eventbus:3.0.0'

然后,我們需要定義事件。事件是POJO(plain old Java object),并沒有特殊的要求:

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

其次,準備訂閱者,訂閱者實現(xiàn)事件處理方法(也稱為“訂閱者方法”)。當事件被發(fā)布時,這些方法會被調用。在EventBus 3中,它們通過 @Subscribe annotation進行聲明,方法名可以自由地進行選擇。而在EventBus 2中,則是通過命名模式來聲明監(jiān)聽者方法的。

// This method will be called when a MessageEvent is posted (in the UI thread for Toast)
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}

// This method will be called when a SomeOtherEvent is posted
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
    doSomethingWith(event);
}

訂閱者需要向EventBus注冊和注銷它自己。只有當訂閱者注冊了,它們才能收到事件。在Android中,Activities和Fragments通常根據(jù)它們的生命周期來進行綁定:

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
   EventBus.getDefault().unregister(this);
    super.onStop();
}

最后即是事件源發(fā)布事件。我們可以在代碼的任何位置發(fā)布事件,當前所有事件類型與發(fā)布的事件類型匹配的訂閱者都將收到它。

EventBus.getDefault().post(new MessageEvent("Hello everyone!"));

EventBus 更多特性說明

了解了EventBus的基本用法之后,我們再來了解一下它提供的一些高級特性,以方便我們后續(xù)理解它的設計決策。

Sticky事件

某些事件攜帶的信息在事件被發(fā)布之后依然有價值。比如,一個指示初始化過程完成的事件信號?;蛘呷绻阌袀鞲衅骰蛭恢脭?shù)據(jù),你想要獲取它們的最新值的情況。不是實現(xiàn)自己的緩存,而是使用sticky事件。EventBus將在內存中保存最新的某一類型的sticky事件。Sticky事件可以被發(fā)送給訂閱者,也可以顯式地查詢。從而使你可以不需要特別的邏輯來考慮已經(jīng)可用的數(shù)據(jù)。這與Android中的sticky broadcast有些類似。

比如,一個sticky事件在一段時間之前被拋出:

EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));

現(xiàn)在,sticky事件被拋出后的某個時刻,一個新的Activity啟動了。在注冊階段所有的sticky訂閱者方法將立即獲得之前拋出的sticky事件:

    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }

    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onEvent(MessageEvent event) {
        // UI updates must run on MainThread
        textField.setText(event.message);
    }

    @Override
    public void onStop() {
        EventBus.getDefault().unregister(this);
        super.onStop();
    }

正如前面看到的,最新的sticky事件會在注冊時立即自動地被傳遞給匹配的訂閱者。但有時手動地去檢查sticky事件可能會更方便一些。有時也可能需要移除(消費)sticky事件以便于它不會再被傳遞。比如:

MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
    // "Consume" the sticky event
    EventBus.getDefault().removeStickyEvent(stickyEvent);
    // Now do something with it
}

removeStickyEvent方法是重載的:當你傳遞class時,它將返回持有的之前的sticky事件。使用如下的變體,我們可以改進前面的例子:

MessageEvent stickyEvent = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
    // Now do something with it
}

優(yōu)先級及事件取消

盡管使用EventBus的大多數(shù)場景不需要優(yōu)先級或事件的取消,但在某些特殊的場景下它們還是很方便的。比如,在app處于前臺時一個事件可能觸發(fā)某些UI邏輯,而如果app當前對用戶不可見則需要有不同的響應。

我們可以定制訂閱者的優(yōu)先級??梢酝ㄟ^在注冊期間給訂閱者提供一個優(yōu)先級來改變事件傳遞的順序。

@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
…
}

在相同的傳遞線程(ThreadMode)內,高優(yōu)先級的訂閱者將先于低優(yōu)先級的訂閱者收到事件。默認的優(yōu)先級是0。優(yōu)先級不影響處于不同ThreadModes中的訂閱者的事件傳遞順序。

我們還可以取消事件的傳遞。我們可以在一個訂閱者的事件處理方法中通過調用cancelEventDelivery(Object event)來取消事件的傳遞進程。所有后續(xù)的事件傳遞將被取消:后面的訂閱者將無法接收到事件。

// Called in the same thread (default)
@Subscribe
public void onEvent(MessageEvent event){
// Process the event
…

EventBus.getDefault().cancelEventDelivery(event) ;
}

事件通常由高優(yōu)先級的訂閱者取消。取消被限制只能在發(fā)布線程ThreadMode.PostThread的事件處理方法中進行。

傳遞線程

EventBus可以幫忙處理線程:事件可以在不同于拋出事件的線程中傳遞。一個常見的使用場景是處理UI變化。在Android中,UI變化必須在UI(主)線程中完成。另一方面,網(wǎng)絡,或任何耗時任務,必須不運行在主線程。EventBus幫忙處理了那些任務并與UI線程同步(不需要深入了解線程事務,使用AsyncTask即可,等等)。

ThreadMode: POSTING

訂閱者將在與拋出事件相同的線程中被調用。這是默認的方式。事件傳遞是同步完成的,事件傳遞完成時,所有的訂閱者將已經(jīng)被調用一次了。這個ThreadMode意味著最小的開銷,因為它完全避免了線程的切換。對于已知耗時非常短的簡單的不需要請求主線程的任務,這是建議采用的模式。使用這個模式的事件處理器應該迅速返回以避免阻塞發(fā)布事件的線程,而后者可能是主線程。比如:

// Called in the same thread (default)

@Subscribe(threadMode = ThreadMode.POSTING) // ThreadMode is optional here
public void onMessage(MessageEvent event) {
    log(event.message);
}

ThreadMode: MAIN

訂閱者將在Android的主線程中被調用(有時被稱為UI線程)。如果發(fā)布事件的線程是主線程,事件處理器方法將會直接被調用(如同ThreadMode.POSTING中描述的那樣)。事件處理器使用這個模式必須快速地返回以避免阻塞主線程。比如:

// Called in Android UI's main thread
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
    textField.setText(event.message);
}

ThreadMode: BACKGROUND

訂閱者將在一個后臺線程中被調用。如果發(fā)布事件的線程不是主線程,事件處理器方法將直接在發(fā)布的線程中被調用。如果發(fā)布線程是主線程,EventBus使用一個單獨的后臺線程,它將順序地傳遞它所有的事件。使用這個模式的事件處理器應該嘗試快速返回以避免阻塞后臺線程。

// Called in the background thread
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
    saveToDisk(event.message);
}

ThreadMode: ASYNC

事件處理器方法在另外一個線程中被調用。這總是獨立于發(fā)布事件的線程和主線程。發(fā)布線程從不等待使用這一模式的事件處理器。對于執(zhí)行比較耗時的事件處理器方法應該使用這個模式,比如對于網(wǎng)絡訪問。避免同時大量地觸發(fā)長時間運行的異步處理器方法而限制并發(fā)線程的數(shù)量。EventBus使用了一個線程池以有效地復用已完成異步事件處理器通知的線程。

// Called in a separate thread
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
    backend.send(event.message);
}

EventBus對象的創(chuàng)建

大體了解了EventBus提供的特性之后,我們來分析EventBus的設計與實現(xiàn)。我們從EventBus對象的創(chuàng)建開始。EventBus對象需要用EventBusBuilder來創(chuàng)建。以EventBus提供的default EventBus對象為例,我們來看一下創(chuàng)建的過程。

應用程序可以通過EventBus.getDefault()來獲取默認的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;
    }

雙重加鎖檢查手法來保證對EventBus.getDefault()調用的線程安全。來看EventBus的構造函數(shù):

    public static EventBusBuilder builder() {
        return new EventBusBuilder();
    }
......
    /**
     * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
     * central bus, consider {@link #getDefault()}.
     */
    public EventBus() {
        this(DEFAULT_BUILDER);
    }

    EventBus(EventBusBuilder builder) {
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();

        mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
        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類提供了一個public的構造函數(shù),可以以默認的配置來構造EventBus對象。EventBusBuilder是繁雜的EventBus構造時所需配置信息的容器。至于利用EventBusBuilder進行配置的配置項具體的含義,暫時先不詳述。

package org.greenrobot.eventbus;

import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Creates EventBus instances with custom parameters and also allows to install a custom default EventBus instance.
 * Create a new builder using {@link EventBus#builder()}.
 */
public class EventBusBuilder {
    private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

    boolean logSubscriberExceptions = true;
    boolean logNoSubscriberMessages = true;
    boolean sendSubscriberExceptionEvent = true;
    boolean sendNoSubscriberEvent = true;
    boolean throwSubscriberException;
    boolean eventInheritance = true;
    boolean ignoreGeneratedIndex;
    boolean strictMethodVerification;
    ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
    List<Class<?>> skipMethodVerificationForClasses;
    List<SubscriberInfoIndex> subscriberInfoIndexes;

    EventBusBuilder() {
    }

    /** Default: true */
    public EventBusBuilder logSubscriberExceptions(boolean logSubscriberExceptions) {
        this.logSubscriberExceptions = logSubscriberExceptions;
        return this;
    }

    /** Default: true */
    public EventBusBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages) {
        this.logNoSubscriberMessages = logNoSubscriberMessages;
        return this;
    }

    /** Default: true */
    public EventBusBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent) {
        this.sendSubscriberExceptionEvent = sendSubscriberExceptionEvent;
        return this;
    }

    /** Default: true */
    public EventBusBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent) {
        this.sendNoSubscriberEvent = sendNoSubscriberEvent;
        return this;
    }

    /**
     * Fails if an subscriber throws an exception (default: false).
     * <p/>
     * Tip: Use this with BuildConfig.DEBUG to let the app crash in DEBUG mode (only). This way, you won't miss
     * exceptions during development.
     */
    public EventBusBuilder throwSubscriberException(boolean throwSubscriberException) {
        this.throwSubscriberException = throwSubscriberException;
        return this;
    }

    /**
     * By default, EventBus considers the event class hierarchy (subscribers to super classes will be notified).
     * Switching this feature off will improve posting of events. For simple event classes extending Object directly,
     * we measured a speed up of 20% for event posting. For more complex event hierarchies, the speed up should be
     * >20%.
     * <p/>
     * However, keep in mind that event posting usually consumes just a small proportion of CPU time inside an app,
     * unless it is posting at high rates, e.g. hundreds/thousands of events per second.
     */
    public EventBusBuilder eventInheritance(boolean eventInheritance) {
        this.eventInheritance = eventInheritance;
        return this;
    }


    /**
     * Provide a custom thread pool to EventBus used for async and background event delivery. This is an advanced
     * setting to that can break things: ensure the given ExecutorService won't get stuck to avoid undefined behavior.
     */
    public EventBusBuilder executorService(ExecutorService executorService) {
        this.executorService = executorService;
        return this;
    }

    /**
     * Method name verification is done for methods starting with onEvent to avoid typos; using this method you can
     * exclude subscriber classes from this check. Also disables checks for method modifiers (public, not static nor
     * abstract).
     */
    public EventBusBuilder skipMethodVerificationFor(Class<?> clazz) {
        if (skipMethodVerificationForClasses == null) {
            skipMethodVerificationForClasses = new ArrayList<>();
        }
        skipMethodVerificationForClasses.add(clazz);
        return this;
    }

    /** Forces the use of reflection even if there's a generated index (default: false). */
    public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
        this.ignoreGeneratedIndex = ignoreGeneratedIndex;
        return this;
    }

    /** Enables strict method verification (default: false). */
    public EventBusBuilder strictMethodVerification(boolean strictMethodVerification) {
        this.strictMethodVerification = strictMethodVerification;
        return this;
    }

    /** Adds an index generated by EventBus' annotation preprocessor. */
    public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if(subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }

    /**
     * Installs the default EventBus returned by {@link EventBus#getDefault()} using this builders' values. Must be
     * done only once before the first usage of the default EventBus.
     *
     * @throws EventBusException if there's already a default EventBus instance in place
     */
    public EventBus installDefaultEventBus() {
        synchronized (EventBus.class) {
            if (EventBus.defaultInstance != null) {
                throw new EventBusException("Default instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior.");
            }
            EventBus.defaultInstance = build();
            return EventBus.defaultInstance;
        }
    }

    /** Builds an EventBus based on the current configuration. */
    public EventBus build() {
        return new EventBus(this);
    }

}

這是一個蠻常規(guī)的Builder。我們看到了自定義可配置項創(chuàng)建EventBus對象的方法,也就是通過EventBusBuilder.build()方法。

EventBusBuilder還提供了一個方法installDefaultEventBus()可以讓我們在創(chuàng)建自定義EventBus對象時,將該對象設置為default EventBus對象。

總結一下EventBus對象創(chuàng)建的方式:

  1. 通過EventBus的public不帶參數(shù)構造函數(shù),創(chuàng)建默認配置的EventBus對象。EventBus庫提供的default EventBus對象的創(chuàng)建方式。
  2. 通過EventBusBuilder創(chuàng)建自定義配置的EventBus對象。這種方式的對象可以通過EventBusBuilder.installDefaultEventBus()在對象被創(chuàng)建的同時設置為default EventBus對象。

由此可見,引入EventBus庫之后,我們應用程序的結構實際上將如下面這樣:


EventBus所提供的特性就先介紹到這里。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • EventBus源碼分析(一) EventBus官方介紹為一個為Android系統(tǒng)優(yōu)化的事件訂閱總線,它不僅可以很...
    蕉下孤客閱讀 4,090評論 4 42
  • 我每周會寫一篇源代碼分析的文章,以后也可能會有其他主題.如果你喜歡我寫的文章的話,歡迎關注我的新浪微博@達達達達s...
    SkyKai閱讀 25,160評論 23 184
  • 對于Android開發(fā)老司機來說肯定不會陌生,它是一個基于觀察者模式的事件發(fā)布/訂閱框架,開發(fā)者可以通過極少的代碼...
    飛揚小米閱讀 1,541評論 0 50
  • 前面在 EventBus設計與實現(xiàn)分析——特性介紹中介紹了EventBus的基本用法,及其提供的大多數(shù)特性的用法;...
    hanpfei閱讀 1,077評論 4 12
  • EventBus 是一個Android端優(yōu)化的 publish/subscribe 消息總線,簡化了應用程序各個組...
    王世軍Steven閱讀 1,938評論 4 21

友情鏈接更多精彩內容