EventBus是一個(gè) 發(fā)布/訂閱
模式的消息總線庫(kù)晰搀,它簡(jiǎn)化了應(yīng)用程序內(nèi)各組件間蕉拢、組件與后臺(tái)線程間的通信,解耦了事件的發(fā)送者和接收者刻两,避免了復(fù)雜的增蹭、易于出錯(cuò)的依賴(lài)及生命周期問(wèn)題,可以使我們的代碼更加簡(jiǎn)潔磅摹、健壯滋迈。
在不使用EventBus的情況下,我們也可能會(huì)使用諸如 Observable/Observer
這樣得一些機(jī)制來(lái)處理事件的監(jiān)聽(tīng)/發(fā)布户誓。如果在我們的應(yīng)用程序中饼灿,有許多地方需要使用事件的監(jiān)聽(tīng)/發(fā)布,則我們應(yīng)用程序的整個(gè)結(jié)構(gòu)可能就會(huì)像下面這個(gè)樣子:
每一處需要用到事件的
監(jiān)聽(tīng)/發(fā)布
的地方帝美,都需要實(shí)現(xiàn)一整套監(jiān)聽(tīng)/發(fā)布
的機(jī)制碍彭,比如定義Listener接口,定義Notification Center/Observable悼潭,定義事件類(lèi)庇忌,定義注冊(cè)監(jiān)聽(tīng)者的方法、移除監(jiān)聽(tīng)者的方法舰褪、發(fā)布事件的方法等皆疹。我們不得不寫(xiě)許多繁瑣的,甚至常常是重復(fù)的冗余的代碼來(lái)實(shí)現(xiàn)我們的設(shè)計(jì)目的占拍。
引入EventBus庫(kù)之后略就,事件的監(jiān)聽(tīng)/發(fā)布
將變得非常簡(jiǎn)單捎迫,我們應(yīng)用程序的結(jié)構(gòu)也將更加簡(jiǎn)潔,會(huì)如下面這樣:
我們可以將事件監(jiān)聽(tīng)者的管理残制,注冊(cè)監(jiān)聽(tīng)者立砸、移除監(jiān)聽(tīng)者,事件發(fā)布等方法等都交給EventBus來(lái)完成初茶,而只定義事件類(lèi)颗祝,實(shí)現(xiàn)事件處理方法即可。
在使用Observable/Observer來(lái)實(shí)現(xiàn)事件的監(jiān)聽(tīng)/發(fā)布
時(shí)恼布,監(jiān)聽(tīng)者和事件發(fā)布者之間的關(guān)聯(lián)關(guān)系非常明晰螺戳,事件發(fā)布者找到事件的監(jiān)聽(tīng)者從不成為問(wèn)題。引入EventBus之后折汞,它會(huì)統(tǒng)一管理所有對(duì)不同類(lèi)型事件感興趣的監(jiān)聽(tīng)者倔幼,則事件發(fā)布者在發(fā)布事件時(shí),能夠準(zhǔn)確高效地找到對(duì)事件感興趣的監(jiān)聽(tīng)者將成為重要的問(wèn)題爽待。后面我們就通過(guò)對(duì)EventBus代碼實(shí)現(xiàn)的分析损同,來(lái)獲取這樣一些重要問(wèn)題的答案,同時(shí)來(lái)欣賞EventBus得設(shè)計(jì)鸟款。
EventBus的基本使用
在具體分析EventBus的代碼之前膏燃,我們先來(lái)了解一下EventBus的用法。想要使用EventBus何什,首先需要在我們應(yīng)用程序的Gradle腳本中添加對(duì)于EventBus的依賴(lài)组哩,以當(dāng)前最新版3.0.0為例:
compile 'org.greenrobot:eventbus:3.0.0'
然后,我們需要定義事件处渣。事件是POJO(plain old Java object)伶贰,并沒(méi)有特殊的要求:
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
其次,準(zhǔn)備訂閱者罐栈,訂閱者實(shí)現(xiàn)事件處理方法(也稱(chēng)為“訂閱者方法”)黍衙。當(dāng)事件被發(fā)布時(shí),這些方法會(huì)被調(diào)用悠瞬。在EventBus 3中们豌,它們通過(guò) @Subscribe annotation進(jìn)行聲明,方法名可以自由地進(jìn)行選擇浅妆。而在EventBus 2中,則是通過(guò)命名模式來(lái)聲明監(jiān)聽(tīng)者方法的障癌。
// 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注冊(cè)和注銷(xiāo)它自己凌外。只有當(dāng)訂閱者注冊(cè)了,它們才能收到事件涛浙。在Android中康辑,Activities和Fragments通常根據(jù)它們的生命周期來(lái)進(jìn)行綁定:
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
最后即是事件源發(fā)布事件摄欲。我們可以在代碼的任何位置發(fā)布事件,當(dāng)前所有事件類(lèi)型與發(fā)布的事件類(lèi)型匹配的訂閱者都將收到它疮薇。
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
EventBus 更多特性說(shuō)明
了解了EventBus的基本用法之后胸墙,我們?cè)賮?lái)了解一下它提供的一些高級(jí)特性,以方便我們后續(xù)理解它的設(shè)計(jì)決策按咒。
Sticky事件
某些事件攜帶的信息在事件被發(fā)布之后依然有價(jià)值迟隅。比如,一個(gè)指示初始化過(guò)程完成的事件信號(hào)励七≈窍或者如果你有傳感器或位置數(shù)據(jù),你想要獲取它們的最新值的情況掠抬。不是實(shí)現(xiàn)自己的緩存吼野,而是使用sticky事件。EventBus將在內(nèi)存中保存最新的某一類(lèi)型的sticky事件两波。Sticky事件可以被發(fā)送給訂閱者瞳步,也可以顯式地查詢(xún)。從而使你可以不需要特別的邏輯來(lái)考慮已經(jīng)可用的數(shù)據(jù)腰奋。這與Android中的sticky broadcast有些類(lèi)似单起。
比如,一個(gè)sticky事件在一段時(shí)間之前被拋出:
EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
現(xiàn)在氛堕,sticky事件被拋出后的某個(gè)時(shí)刻馏臭,一個(gè)新的Activity啟動(dòng)了。在注冊(cè)階段所有的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事件會(huì)在注冊(cè)時(shí)立即自動(dòng)地被傳遞給匹配的訂閱者括儒。但有時(shí)手動(dòng)地去檢查sticky事件可能會(huì)更方便一些。有時(shí)也可能需要移除(消費(fèi))sticky事件以便于它不會(huì)再被傳遞锐想。比如:
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
方法是重載的:當(dāng)你傳遞class時(shí)帮寻,它將返回持有的之前的sticky事件。使用如下的變體赠摇,我們可以改進(jìn)前面的例子:
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)先級(jí)及事件取消
盡管使用EventBus的大多數(shù)場(chǎng)景不需要優(yōu)先級(jí)或事件的取消固逗,但在某些特殊的場(chǎng)景下它們還是很方便的。比如藕帜,在app處于前臺(tái)時(shí)一個(gè)事件可能觸發(fā)某些UI邏輯烫罩,而如果app當(dāng)前對(duì)用戶(hù)不可見(jiàn)則需要有不同的響應(yīng)。
我們可以定制訂閱者的優(yōu)先級(jí)洽故”丛埽可以通過(guò)在注冊(cè)期間給訂閱者提供一個(gè)優(yōu)先級(jí)來(lái)改變事件傳遞的順序。
@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
…
}
在相同的傳遞線程(ThreadMode
)內(nèi)时甚,高優(yōu)先級(jí)的訂閱者將先于低優(yōu)先級(jí)的訂閱者收到事件隘弊。默認(rèn)的優(yōu)先級(jí)是0哈踱。優(yōu)先級(jí)不影響處于不同ThreadModes中的訂閱者的事件傳遞順序。
我們還可以取消事件的傳遞梨熙。我們可以在一個(gè)訂閱者的事件處理方法中通過(guò)調(diào)用cancelEventDelivery(Object event)
來(lái)取消事件的傳遞進(jìn)程开镣。所有后續(xù)的事件傳遞將被取消:后面的訂閱者將無(wú)法接收到事件。
// Called in the same thread (default)
@Subscribe
public void onEvent(MessageEvent event){
// Process the event
…
EventBus.getDefault().cancelEventDelivery(event) ;
}
事件通常由高優(yōu)先級(jí)的訂閱者取消咽扇。取消被限制只能在發(fā)布線程ThreadMode.PostThread
的事件處理方法中進(jìn)行邪财。
傳遞線程
EventBus可以幫忙處理線程:事件可以在不同于拋出事件的線程中傳遞。一個(gè)常見(jiàn)的使用場(chǎng)景是處理UI變化肌割。在Android中卧蜓,UI變化必須在UI(主)線程中完成。另一方面把敞,網(wǎng)絡(luò)弥奸,或任何耗時(shí)任務(wù),必須不運(yùn)行在主線程奋早。EventBus幫忙處理了那些任務(wù)并與UI線程同步(不需要深入了解線程事務(wù)盛霎,使用AsyncTask即可,等等)耽装。
ThreadMode: POSTING
訂閱者將在與拋出事件相同的線程中被調(diào)用愤炸。這是默認(rèn)的方式。事件傳遞是同步完成的掉奄,事件傳遞完成時(shí)规个,所有的訂閱者將已經(jīng)被調(diào)用一次了。這個(gè)ThreadMode
意味著最小的開(kāi)銷(xiāo)姓建,因?yàn)樗耆苊饬司€程的切換诞仓。對(duì)于已知耗時(shí)非常短的簡(jiǎn)單的不需要請(qǐng)求主線程的任務(wù),這是建議采用的模式速兔。使用這個(gè)模式的事件處理器應(yīng)該迅速返回以避免阻塞發(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的主線程中被調(diào)用(有時(shí)被稱(chēng)為UI線程)涣狗。如果發(fā)布事件的線程是主線程谍婉,事件處理器方法將會(huì)直接被調(diào)用(如同ThreadMode.POSTING
中描述的那樣)。事件處理器使用這個(gè)模式必須快速地返回以避免阻塞主線程镀钓。比如:
// Called in Android UI's main thread
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
textField.setText(event.message);
}
ThreadMode: BACKGROUND
訂閱者將在一個(gè)后臺(tái)線程中被調(diào)用穗熬。如果發(fā)布事件的線程不是主線程,事件處理器方法將直接在發(fā)布的線程中被調(diào)用丁溅。如果發(fā)布線程是主線程死陆,EventBus使用一個(gè)單獨(dú)的后臺(tái)線程,它將順序地傳遞它所有的事件唧瘾。使用這個(gè)模式的事件處理器應(yīng)該嘗試快速返回以避免阻塞后臺(tái)線程措译。
// Called in the background thread
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
saveToDisk(event.message);
}
ThreadMode: ASYNC
事件處理器方法在另外一個(gè)線程中被調(diào)用。這總是獨(dú)立于發(fā)布事件的線程和主線程饰序。發(fā)布線程從不等待使用這一模式的事件處理器领虹。對(duì)于執(zhí)行比較耗時(shí)的事件處理器方法應(yīng)該使用這個(gè)模式,比如對(duì)于網(wǎng)絡(luò)訪問(wèn)求豫。避免同時(shí)大量地觸發(fā)長(zhǎng)時(shí)間運(yùn)行的異步處理器方法而限制并發(fā)線程的數(shù)量塌衰。EventBus
使用了一個(gè)線程池以有效地復(fù)用已完成異步事件處理器通知的線程。
// Called in a separate thread
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
backend.send(event.message);
}
EventBus對(duì)象的創(chuàng)建
大體了解了EventBus
提供的特性之后蝠嘉,我們來(lái)分析EventBus
的設(shè)計(jì)與實(shí)現(xiàn)最疆。我們從EventBus
對(duì)象的創(chuàng)建開(kāi)始。EventBus
對(duì)象需要用EventBusBuilder
來(lái)創(chuàng)建蚤告。以EventBus
提供的default EventBus
對(duì)象為例努酸,我們來(lái)看一下創(chuàng)建的過(guò)程。
應(yīng)用程序可以通過(guò)EventBus.getDefault()
來(lái)獲取默認(rèn)的EventBus
對(duì)象:
/** 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;
}
雙重加鎖檢查手法來(lái)保證對(duì)EventBus.getDefault()
調(diào)用的線程安全杜恰。來(lái)看EventBus
的構(gòu)造函數(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
類(lèi)提供了一個(gè)public的構(gòu)造函數(shù)获诈,可以以默認(rèn)的配置來(lái)構(gòu)造EventBus
對(duì)象。EventBusBuilder
是繁雜的EventBus
構(gòu)造時(shí)所需配置信息的容器心褐。至于利用EventBusBuilder
進(jìn)行配置的配置項(xiàng)具體的含義舔涎,暫時(shí)先不詳述。
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);
}
}
這是一個(gè)蠻常規(guī)的Builder逗爹。我們看到了自定義可配置項(xiàng)創(chuàng)建EventBus
對(duì)象的方法亡嫌,也就是通過(guò)EventBusBuilder.build()
方法。
EventBusBuilder
還提供了一個(gè)方法installDefaultEventBus()
可以讓我們?cè)趧?chuàng)建自定義EventBus
對(duì)象時(shí)掘而,將該對(duì)象設(shè)置為default EventBus
對(duì)象挟冠。
總結(jié)一下EventBus對(duì)象創(chuàng)建的方式:
- 通過(guò)
EventBus
的public不帶參數(shù)構(gòu)造函數(shù),創(chuàng)建默認(rèn)配置的EventBus
對(duì)象镣屹。EventBus庫(kù)提供的defaultEventBus
對(duì)象的創(chuàng)建方式圃郊。 - 通過(guò)
EventBusBuilder
創(chuàng)建自定義配置的EventBus
對(duì)象。這種方式的對(duì)象可以通過(guò)EventBusBuilder.installDefaultEventBus()
在對(duì)象被創(chuàng)建的同時(shí)設(shè)置為defaultEventBus
對(duì)象女蜈。
由此可見(jiàn)持舆,引入EventBus庫(kù)之后,我們應(yīng)用程序的結(jié)構(gòu)實(shí)際上將如下面這樣:
EventBus所提供的特性就先介紹到這里伪窖。