title: android 初識(shí)EventBus
date: 2016-04-17
tags: eventbus
本文檔基于EventBus 3.0.0
EventBus是一個(gè)發(fā)布/訂閱事件總線,用來(lái)優(yōu)化android程序锅知。
EventBus是什么
- 簡(jiǎn)化組件之間通信砸烦。
- 解耦事件的發(fā)送者和接受者。
- 在Activities,F(xiàn)ragments和后臺(tái)線程之間表現(xiàn)的很好
- 避免復(fù)雜的和容易出錯(cuò)的依賴和生命周期
- 使代碼簡(jiǎn)單
- 快雳灵,高性能:特別是在注重性能的Android上。也許在其同類的解決方案是最快的闸盔。
- 忻跽蕖(jar包小于50K),但是強(qiáng)大:EventBus是一個(gè)很小的庫(kù)迎吵,它的API超級(jí)簡(jiǎn)單躲撰。但是你的軟件架構(gòu)會(huì)非常受益于組件解耦:當(dāng)使用事件的時(shí)候,訂閱者不需要知道發(fā)送者是誰(shuí)击费。
- 在實(shí)踐中被100,000,000+的應(yīng)用安裝測(cè)試拢蛋。
- 有在線程間傳遞,訂閱優(yōu)先級(jí)等高級(jí)特性
- 基于方便的注解(不犧牲性能):只需要通過(guò)在你的訂閱方法放置@Subscribe注解蔫巩。因?yàn)槭窃诰幾g的時(shí)候索引注解谆棱,EventBus不需要在app運(yùn)行時(shí)間做注解反射(在android上會(huì)很慢)快压。
- android主線程傳遞:當(dāng)和UI交互的時(shí)候,無(wú)論這個(gè)事件是怎么提交的垃瞧,EventBus都可以在主線程傳遞事件蔫劣。
- 后臺(tái)線程傳遞:如果訂閱者運(yùn)行長(zhǎng)時(shí)間的任務(wù),EventBus也可以使用后臺(tái)線程來(lái)避免UI阻塞个从。
- 事件和訂閱繼承:在EventBus中脉幢,事件和訂閱類都是面向?qū)ο蟮姆独J录嗀是B的父類嗦锐。提交B類型的事件也會(huì)提交給對(duì)A感興趣的訂閱者嫌松。類似的訂閱者類也是如此。
- 0配置:在代碼中任何位置可以立刻使用一個(gè)默認(rèn)的EventBus實(shí)例意推。
- 可配置:可以按需求調(diào)整EventBus豆瘫,可以使用建造者模式調(diào)整行為。
- EventBus和Android的廣播和Intent系統(tǒng)有什么不同
不像Android的廣播和intent系統(tǒng)菊值,EventBus使用標(biāo)準(zhǔn)的java類作為事件并且提供更多的方便的API外驱。EventBus更多的使用場(chǎng)景是不想設(shè)置麻煩的intent,設(shè)置intent extras腻窒,實(shí)現(xiàn)廣播接收者昵宇,再提取intent extras。而且開銷更低儿子。
添加到你的工程
EventBus可以從JCenter和Maven中央倉(cāng)庫(kù)獲取瓦哎,所以只需在gradle腳本中添加這個(gè)依賴:
complie 'org.greenrobot:eventbus:3.0.0'
開始使用EventBus
使用EventBus只需3步。在此之前先在Gradle腳本中添加依賴柔逼。
complie '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;
}
}
第二步:準(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();
}
第三步:提交事件
在代碼中任意位置提交事件瞬哼。所有當(dāng)前注冊(cè)的匹配事件類型的訂閱者都會(huì)收到事件。
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
線程間傳遞(線程模式)
事件可以在不同線程間傳遞租副。典型的使用就是用來(lái)處理UI改變坐慰,網(wǎng)絡(luò)操作,或者耗時(shí)的操作用僧。EventBus可以處理這些任務(wù)并同步UI線程(不必再考慮線程轉(zhuǎn)換结胀,使用AsyncTask两残,等)。
有四種線程模式:
線程模式:POSTING
默認(rèn)情況下把跨,訂閱者在被提交事件的線程被調(diào)用人弓。事件同步完成傳遞,一旦事件提交所有的訂閱者都會(huì)被調(diào)用着逐。因?yàn)楸苊饬司€程切換崔赌,這種模式意味著開銷最小。所以對(duì)那些花費(fèi)很短的時(shí)間來(lái)完成耸别,不需要在主線程中的簡(jiǎn)單的任務(wù)健芭,推薦使用這種模式。事件處理程序使用這種模式應(yīng)該是很快能返回的秀姐,避免阻塞提交線程比如主線程慈迈。比如:
@Subscribe(threadMode = ThreadMode.POSTING) // ThreadMode is optional here
public void onMessage(MessageEvent event) {
log(event.message);
}
線程模式:MAIN
訂閱者會(huì)在主線程被調(diào)用。如果提交的線程是主線程省有,事件處理方法會(huì)直接被調(diào)用(就像Thread.POSTING描述的那樣同步)痒留。事件處理程序使用這個(gè)模式必須能夠快速返回避免阻塞主線程。比如:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
textField.setText(event.message);
}
線程模式:BACKGROUND
訂閱者會(huì)在后臺(tái)線程被調(diào)用蠢沿。如果不是在主線程提交伸头,事件處理方法會(huì)直接在提交線程被調(diào)用。如果是在主線程提交舷蟀,EventBus使用一個(gè)單獨(dú)的后臺(tái)線程按順序傳遞所有事件恤磷。事件處理使用這種模式應(yīng)該能夠盡快返回避免阻塞后臺(tái)線程。
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
saveToDisk(event.message);
}
線程模式:ASYNC
事件處理方法會(huì)在一個(gè)單獨(dú)的線程被調(diào)用野宜。這個(gè)線程通常和提交的線程和主線程是獨(dú)立的扫步。提交的事件從不等待事件處理方法。事件處理方法如果需要花費(fèi)一段時(shí)間比如訪問(wèn)網(wǎng)絡(luò)應(yīng)該使用這種模式匈子。避免在同一時(shí)間觸發(fā)大量長(zhǎng)時(shí)間異步的處理方法來(lái)限制并發(fā)的線程河胎。EventBus使用一個(gè)線程池來(lái)有效重復(fù)利用線程完成的異步事件處理程序的通知。
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
backend.send(event.message);
}
配置
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。
Sticky Event
一些事件攜帶在事件提交之后仍然感興趣的信息古涧。比如垂券,一個(gè)事件標(biāo)記一些初始化完成或者一些傳感器或位置數(shù)據(jù)的最新的值∠刍可以使用sticky事件來(lái)代替你自己的實(shí)現(xiàn)菇爪。EventBus在內(nèi)存中保持某一個(gè)類型的最后的sticky事件。這個(gè)sticky事件可以傳遞到訂閱者或者也可以被明確的查詢柒昏。因此娄帖,不需要任何特殊的邏輯來(lái)考慮已經(jīng)可用的數(shù)據(jù)。
Sticky實(shí)例
一個(gè)sticky事件是一段時(shí)間之前被提交的昙楚。
EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
現(xiàn)在近速,sticky事件被提交了,一個(gè)新的activity啟動(dòng)堪旧。在注冊(cè)過(guò)程中所有的sticky的訂閱者方法都會(huì)立刻獲取到之前提交的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();
}
手動(dòng)獲取和移除sticky事件
就像前一段說(shuō)的那樣削葱,最后的sticky事件在訂閱者注冊(cè)的時(shí)候會(huì)自動(dòng)傳遞。但是淳梦,有時(shí)候手動(dòng)檢測(cè)sticky事件更方便析砸。有時(shí)候他們不再傳遞的時(shí)候需要移除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方法是超載的:當(dāng)傳入一個(gè)類爆袍,它會(huì)返回之前保持的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)先級(jí)和事件取消
大部分的EventBus都不需要優(yōu)先級(jí)或事件取消陨囊,但是它們?cè)谔厥馇闆r下會(huì)派上用場(chǎng)弦疮。比如,一個(gè)app在后臺(tái)運(yùn)行的時(shí)候一個(gè)事件觸發(fā)了一些UI的邏輯 蜘醋,但是如果app當(dāng)前對(duì)用戶是不可見(jiàn)的胁塞,那么應(yīng)該有不同的反應(yīng)。
訂閱者優(yōu)先級(jí)
可以通過(guò)在注冊(cè)期間提供的優(yōu)先級(jí)來(lái)改變事件傳遞的順序沙郭。
@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
…
}
在相同的傳遞線程(ThreadMode)鼻听,高優(yōu)先級(jí)的訂閱者接收事件會(huì)在低優(yōu)先級(jí)之前。默認(rèn)優(yōu)先級(jí)是0仙辟。
注意:優(yōu)先級(jí)不會(huì)對(duì)不同ThreadModes訂閱者之間的傳遞順序有影響扰才。
取消事件傳遞
在訂閱者事件處理方法中通過(guò)cancelEventDelivery(Object event)取消事件傳遞允懂。之后的任何事件傳遞都會(huì)取消:后續(xù)的訂閱者不會(huì)再接收到這個(gè)事件。
// Called in the same thread (default)
@Subscribe
public void onEvent(MessageEvent event){
// Process the event
…
EventBus.getDefault().cancelEventDelivery(event) ;
}
事件通常是被優(yōu)先級(jí)高的訂閱者取消衩匣。取消對(duì)于運(yùn)行在提交線程ThreadMode.PostThread的事件處理方法是被限制的累驮。
訂閱者索引
訂閱者索引(subscriber index)是EventBus3的新特性。這是個(gè)可選的優(yōu)化項(xiàng)來(lái)提速初始化訂閱者注冊(cè)舵揭。訂閱者索引可以使用EventBus的注解處理器在編譯階段創(chuàng)建谤专。雖然使用索引不是必須的,但是android最佳實(shí)踐還是推薦使用午绳。
索引先決條件
注意只有訂閱者和事件類是public置侍,@Subscriber方法才能被索引。由于java的注解本身處理的技術(shù)限制拦焚,@Subscribe注解不會(huì)被匿名內(nèi)部類識(shí)別蜡坊。當(dāng)EventBus不能使用索引的時(shí)候,它會(huì)自動(dòng)回退到在運(yùn)行時(shí)起作用赎败。所以它仍然起作用秕衙,只是慢了一點(diǎn)。
生成索引
為了能夠生成索引僵刮,需要在android-apt gradle插件的編譯環(huán)境添加EventBus注解處理器据忘。還需要傳遞一個(gè)參數(shù)為“eventBusIndex”到指定完全限定的想要生成索引的類。添加下面的部分到Gradle編譯腳本:
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
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"
}
}
當(dāng)下次編譯工程的時(shí)候搞糕,“eventBusIndex”這個(gè)類會(huì)生成勇吊。當(dāng)設(shè)置EventBus的時(shí)候,只需要這樣:
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
或者想使用默認(rèn)的實(shí)例窍仰,可以這樣:
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
索引Libraries
應(yīng)用相同的原理來(lái)索引Library的一部分代碼(并不是最終的應(yīng)用程序)汉规。這樣,可以會(huì)生成多個(gè)索引類驹吮,在設(shè)置EventBus的時(shí)候這樣使:
EventBus eventBus = EventBus.builder()
.addIndex(new MyEventBusAppIndex())
.addIndex(new MyEventBusLibIndex()).build();
混淆
混淆器混淆方法名稱的時(shí)候针史,有可能會(huì)移除方法那些沒(méi)有調(diào)用的方法。因?yàn)橛嗛喎椒](méi)有被直接調(diào)用碟狞,混淆器會(huì)誤認(rèn)為他們沒(méi)有被使用啄枕。在ProGuard配置中使用下面的片段來(lái)保護(hù)訂閱者被移除:
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
注意:不管是否使用索引,都需要這么配置篷就。
AsyncExecutor
免責(zé)聲明:AsyncExecutor不是一個(gè)核心的utility類射亏。在處理后臺(tái)線程它也許會(huì)保存一些錯(cuò)誤代碼,但是它不是一個(gè)核心的EventBus類竭业。
AsyncExecutor像一個(gè)線程池智润,但是有錯(cuò)誤(異常)處理。錯(cuò)誤會(huì)拋出異常未辆,AsyncExecutor會(huì)在事件中包裹這些異常窟绷。
通常調(diào)用AsyncExecutor.create()方法在Application中來(lái)創(chuàng)建一個(gè)實(shí)例。實(shí)現(xiàn)RunnableEx接口來(lái)執(zhí)行咐柜。RunnableEx和Runnable不一樣兼蜈,RunnableEx會(huì)拋出異常。
如果RunnableEx的實(shí)現(xiàn)拋出一個(gè)異常拙友,它會(huì)在ThrowableFailureEvent中捕獲到为狸。
執(zhí)行的例子:
AsyncExecutor.create().execute(
new RunnableEx {
public void run throws LoginException {
// No need to catch any Exception (here: LoginException)
remote.login();
EventBus.getDefault().postSticky(new LoggedInEvent());
}
}
}
接收的例子:
public void onEventMainThread(LoggedInEvent event) {
// Change some UI
}
public void onEventMainThread(ThrowableFailureEvent event) {
// Show error in UI
}
AsyncExecutor生成器
使用靜態(tài)的Async Executor來(lái)自定義Async Executor實(shí)例。它會(huì)返回一個(gè)生成器來(lái)自定義EventBus實(shí)例遗契,線程池辐棒,和失敗的事件類。
另一個(gè)可以自定義的選項(xiàng)是提供錯(cuò)誤事件上下文環(huán)境信息的執(zhí)行范圍牍蜂。比如漾根,一個(gè)錯(cuò)誤事件可能只關(guān)聯(lián)到一個(gè)指定的Activity實(shí)例或類。你自定義錯(cuò)誤事件實(shí)現(xiàn)了HasExecutionScope接口鲫竞,AsyncExecutor會(huì)自動(dòng)設(shè)置執(zhí)行范圍辐怕。這樣,訂閱者可以在它執(zhí)行范圍查詢錯(cuò)誤的事件并基于它做出相應(yīng)操作从绘。