EventBus是一個(gè)基于觀察者模式的事件發(fā)布/訂閱框架圃泡,開(kāi)發(fā)者通過(guò)極少的代碼去實(shí)現(xiàn)多個(gè)模塊之間的通信垮抗,而不需要以層層傳遞接口的形式去單獨(dú)構(gòu)建通信橋梁虏束。從而降低因多重回調(diào)導(dǎo)致的模塊間強(qiáng)耦合洪添,同時(shí)避免產(chǎn)生大量?jī)?nèi)部類绅项。擁有使用方便曲尸,性能高赋续,接入成本低,支持多線程的優(yōu)點(diǎn)另患。
一蚕捉、如何實(shí)現(xiàn)Eventbus
1 定義事件
事件是POJO(plain old java object)類型,不需要什么特別的需求
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
2 準(zhǔn)備訂閱者
訂閱者實(shí)現(xiàn)事件處理方法(也叫做訂閱者方法),這個(gè)方法會(huì)在事件提交的時(shí)候被調(diào)用柴淘。這些是使用@Subscribe注解定義的迫淹。請(qǐng)注意EventBus 3的方法名字可以自由選擇(不像EventBus 2中約束的那樣)。
// 當(dāng)一個(gè)Message Event提交的時(shí)候這個(gè)方法會(huì)被調(diào)用
@Subscribe
public void onMessageEvent(MessageEvent event){
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
// 當(dāng)一個(gè)SomeOtherEvent被提交的時(shí)候這個(gè)方法被調(diào)用为严。
@Subscribe
public void handleSomethingElse(SomeOtherEvent event){
doSomethingWith(event);
}
訂閱者也需要在bus中注冊(cè)和注銷敛熬。只有在訂閱者注冊(cè)的時(shí)候,他們才會(huì)收到事件第股。在Android中应民,Activities和Fragments通常綁定他們的生命周期。
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
3 提交事件
在代碼中任意位置提交事件夕吻。所有當(dāng)前注冊(cè)的匹配事件類型的訂閱者都會(huì)收到事件诲锹。
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
4 線程間傳遞(線程模式)
在EventBus的事件處理函數(shù)中需要指定線程模型,即指定事件處理函數(shù)運(yùn)行所在的想線程涉馅。在上面我們已經(jīng)接觸到了EventBus的四種線程模型归园。那他們有什么區(qū)別呢?
在EventBus中的觀察者通常有四種線程模型稚矿,分別是PostThread(默認(rèn))庸诱、MainThread捻浦、BackgroundThread與Async。
PostThread:
如果使用事件處理函數(shù)指定了線程模型為PostThread桥爽,那么該事件在哪個(gè)線程發(fā)布出來(lái)的朱灿,事件處理函數(shù)就會(huì)在這個(gè)線程中運(yùn)行,也就是說(shuō)發(fā)布事件和接收事件在同一個(gè)線程钠四。在線程模型為PostThread的事件處理函數(shù)中盡量避免執(zhí)行耗時(shí)操作盗扒,因?yàn)樗鼤?huì)阻塞事件的傳遞,甚至有可能會(huì)引起ANR缀去。
MainThread:
如果使用事件處理函數(shù)指定了線程模型為MainThread环疼,那么不論事件是在哪個(gè)線程中發(fā)布出來(lái)的,該事件處理函數(shù)都會(huì)在UI線程中執(zhí)行朵耕。該方法可以用來(lái)更新UI炫隶,但是不能處理耗時(shí)操作。
BackgroundThread:
如果使用事件處理函數(shù)指定了線程模型為BackgroundThread阎曹,那么如果事件是在UI線程中發(fā)布出來(lái)的伪阶,那么該事件處理函數(shù)就會(huì)在新的線程中運(yùn)行,如果事件本來(lái)就是子線程中發(fā)布出來(lái)的处嫌,那么該事件處理函數(shù)直接在發(fā)布事件的線程中執(zhí)行栅贴。在此事件處理函數(shù)中禁止進(jìn)行UI更新操作。
Async:
如果使用事件處理函數(shù)指定了線程模型為Async熏迹,那么無(wú)論事件在哪個(gè)線程發(fā)布檐薯,該事件處理函數(shù)都會(huì)在新建的子線程中執(zhí)行。同樣注暗,此事件處理函數(shù)中禁止進(jìn)行UI更新操作坛缕。
@Subscribe(threadMode = ThreadMode.PostThread)
public void onMessageEventPostThread(MessageEvent messageEvent) {
Log.e("PostThread", Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.MainThread)
public void onMessageEventMainThread(MessageEvent messageEvent) {
Log.e("MainThread", Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.BackgroundThread)
public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
Log.e("BackgroundThread", Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.Async)
public void onMessageEventAsync(MessageEvent messageEvent) {
Log.e("Async", Thread.currentThread().getName());
}
分別使用上面四個(gè)方法訂閱同一事件,打印他們運(yùn)行所在的線程捆昏。首先我們?cè)赨I線程中發(fā)布一條MessageEvent的消息赚楚,看下日志打印結(jié)果是什么。
findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("postEvent", Thread.currentThread().getName());
EventBus.getDefault().post(new MessageEvent());
}
});
打印結(jié)果如下:
log --> E/postEvent﹕ main
log --> E/PostThread﹕ main
log --> E/Async﹕ pool-1-thread-1
log --> E/MainThread﹕ main
log --> E/BackgroundThread﹕ pool-1-thread-2
從日志打印結(jié)果可以看出骗卜,如果在UI線程中發(fā)布事件宠页,則線程模型為PostThread的事件處理函數(shù)也執(zhí)行在UI線程,與發(fā)布事件的線程一致寇仓。線程模型為Async的事件處理函數(shù)執(zhí)行在名字叫做pool-1-thread-1的新的線程中举户。而MainThread的事件處理函數(shù)執(zhí)行在UI線程,BackgroundThread的時(shí)間處理函數(shù)執(zhí)行在名字叫做pool-1-thread-2的新的線程中遍烦。
我們?cè)倏纯丛谧泳€程中發(fā)布一條MessageEvent的消息時(shí)俭嘁,會(huì)有什么樣的結(jié)果。
findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Log.e("postEvent", Thread.currentThread().getName());
EventBus.getDefault().post(new MessageEvent());
}
}).start();
}
});
打印結(jié)果如下:
log --> E/postEvent﹕ Thread-125
log --> E/PostThread﹕ Thread-125
log --> E/BackgroundThread﹕ Thread-125
log --> E/Async﹕ pool-1-thread-1
log --> E/MainThread﹕ main
從日志打印結(jié)果可以看出乳愉,如果在子線程中發(fā)布事件兄淫,則線程模型為PostThread的事件處理函數(shù)也執(zhí)行在子線程屯远,與發(fā)布事件的線程一致(都是Thread-125)蔓姚。BackgroundThread事件模型也與發(fā)布事件在同一線程執(zhí)行捕虽。Async則在一個(gè)名叫pool-1-thread-1的新線程中執(zhí)行。MainThread還是在UI線程中執(zhí)行坡脐。
上面一個(gè)例子充分驗(yàn)證了指定不同線程模型的事件處理方法執(zhí)行所在的線程泄私。
5 黏性事件
除了上面講的普通事件外,EventBus還支持發(fā)送黏性事件备闲。何為黏性事件呢晌端?簡(jiǎn)單講,就是在發(fā)送事件之后再訂閱該事件也能收到該事件恬砂,跟黏性廣播類似咧纠。具體用法如下:
訂閱黏性事件:
EventBus.getDefault().register(StickyModeActivity.this);
黏性事件處理函數(shù):
@Subscribe(sticky = true)
public void XXX(MessageEvent messageEvent) {
......
}
發(fā)送黏性事件:
EventBus.getDefault().postSticky(new MessageEvent("test"));
處理消息事件以及取消訂閱和上面方式相同。
看個(gè)簡(jiǎn)單的黏性事件的例子泻骤,為了簡(jiǎn)單起見(jiàn)我這里就在一個(gè)Activity里演示了漆羔。
Activity代碼:
public class StickyModeActivity extends AppCompatActivity {
int index = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sticky_mode);
findViewById(R.id.post).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().postSticky(new MessageEvent("test" + index++));
}
});
findViewById(R.id.regist).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().registerSticky(StickyModeActivity.this);
}
});
findViewById(R.id.unregist).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().unregister(StickyModeActivity.this);
}
});
}
@Subscribe(threadMode = ThreadMode.PostThread, sticky = true)
public void onMessageEventPostThread(MessageEvent messageEvent) {
Log.e("PostThread", messageEvent.getMessage());
}
@Subscribe(threadMode = ThreadMode.MainThread, sticky = true)
public void onMessageEventMainThread(MessageEvent messageEvent) {
Log.e("MainThread", messageEvent.getMessage());
}
@Subscribe(threadMode = ThreadMode.BackgroundThread, sticky = true)
public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
Log.e("BackgroundThread", messageEvent.getMessage());
}
@Subscribe(threadMode = ThreadMode.Async, sticky = true)
public void onMessageEventAsync(MessageEvent messageEvent) {
Log.e("Async", messageEvent.getMessage());
}
}
布局代碼activity_sticky_mode.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.lling.eventbusdemo.StickyModeActivity">
<Button
android:id="@+id/post"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Post"/>
<Button
android:id="@+id/regist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Regist"/>
<Button
android:id="@+id/unregist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UnRegist"/>
</LinearLayout>
代碼很簡(jiǎn)單擦剑,界面上三個(gè)按鈕璧帝,一個(gè)用來(lái)發(fā)送黏性事件温技,一個(gè)用來(lái)訂閱事件永部,還有一個(gè)用來(lái)取消訂閱的赁遗。首先在未訂閱的情況下點(diǎn)擊發(fā)送按鈕發(fā)送一個(gè)黏性事件邑跪,然后點(diǎn)擊訂閱榜轿,會(huì)看到日志打印結(jié)果如下:
log --> E/PostThread﹕ test0
log --> E/Async﹕ test0
log --> E/MainThread﹕ test0
log --> E/BackgroundThread﹕ test0
這就是粘性事件亥贸,能夠收到訂閱之前發(fā)送的消息器虾。但是它只能收到最新的一次消息讯嫂,比如說(shuō)在未訂閱之前已經(jīng)發(fā)送了多條黏性消息了,然后再訂閱只能收到最近的一條消息兆沙。這個(gè)我們可以驗(yàn)證一下端姚,我們連續(xù)點(diǎn)擊5次POST按鈕發(fā)送5條黏性事件,然后再點(diǎn)擊REGIST按鈕訂閱挤悉,打印結(jié)果如下:
log --> E/PostThread﹕ test4
log --> E/MainThread﹕ test4
log --> E/Async﹕ test4
log --> E/BackgroundThread﹕ test4
由打印結(jié)果可以看出渐裸,確實(shí)是只收到最近的一條黏性事件。
6 配置
EventBusBuilder用來(lái)配置EventBus装悲。比如昏鹃,如果一個(gè)提交的事件沒(méi)有訂閱者,可以使EventBus保持安靜诀诊。
EventBus eventBus = EventBus.builder().logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false).build()
另一個(gè)例子是當(dāng)一個(gè)訂閱者拋出一個(gè)異常的失敗洞渤。注意:默認(rèn)情況下,EventBus捕獲異常從onEvent方法中拋出并且發(fā)出一個(gè)SubscriberExceptionEvent 属瓣,這個(gè)事件可以不必處理载迄。
EventBus eventBus = EventBus.builder().throwSubscriberException(true).build();
配置默認(rèn)EventBus實(shí)例使用EventBus.getDefault()是一種簡(jiǎn)單的方法來(lái)獲取共享的EventBus實(shí)例讯柔。EventBusBuilder也可以使用installDefaultEventBus()方法來(lái)配置這個(gè)默認(rèn)的實(shí)例。比如护昧,當(dāng)在onEvent方法中發(fā)生異常的時(shí)候魂迄,可以配置默認(rèn)的EventBus實(shí)例來(lái)重新拋出異常。建議在使用DEBUG模式的時(shí)候這么使用惋耙,因?yàn)檫@樣app會(huì)因?yàn)檫@個(gè)異常而崩潰捣炬。
EventBus.builder().throwSubscriberException(BuildConfig.DEBUG)
.installDefaultEventBus();
注意:只有在默認(rèn)EventBus實(shí)例在第一次使用之前這么配置一次。后續(xù)調(diào)用installDefaultEventBus() 會(huì)拋出異常绽榛。這確保應(yīng)用程序的行為一致湿酸。可以在Application類中配置默認(rèn)的EventBus灭美。
7 訂閱者索引
對(duì)于上面所描述的EventBus的功能推溃,是通過(guò)Java反射來(lái)獲取訂閱方法,這樣以來(lái)大大降低了EventBus的效率届腐,同時(shí)也影響了我們應(yīng)用程序的效率铁坎。其實(shí)對(duì)于反射的處理解析不僅僅只能夠通過(guò)Java反射的方式來(lái)進(jìn)行,還能夠通過(guò)apt(Annotation Processing Tool)來(lái)處理梯捕。為了提高效率厢呵,EventBus提供這中方式來(lái)完成EventBus的執(zhí)行過(guò)程。下面就來(lái)看一下對(duì)于EventBus的另一種使用方式傀顾。
在Project的build.gradle中添加如下代碼:
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
然后在app的build.gradle中添加如下代碼襟铭。
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
arguments {
eventBusIndex "com.example.myapp.MyEventBusIndex"
}
}
重新rebuild之后會(huì)在build目錄下面生成MyEventBusIndex文件,文件名可以自定義短曾。下面就來(lái)看一下如何使用這個(gè)MyEventBusIndex.我們可以自定義設(shè)置自己的EventBus來(lái)為其添加MyEventBusIndex對(duì)象寒砖。代碼如下所示:
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
我們也能夠?qū)yEventBusIndex對(duì)象安裝在默認(rèn)的EventBus對(duì)象當(dāng)中。代碼如下所示:
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
剩下操作與之前EventBus的一樣嫉拐。當(dāng)然也建議通過(guò)添加訂閱者索引這種方式來(lái)使用EventBus哩都,這樣會(huì)比通過(guò)反射的方式來(lái)解析注解效率更高。