前言
上一篇寫(xiě)了EventBus的基本用法胚股,傳送門(mén):
Android開(kāi)源庫(kù)——EventBus使用教程
在上一篇中只是簡(jiǎn)單展示了EventBus的基本用法捻勉,其實(shí)還有很多好玩和強(qiáng)大的功能压固,那么在本篇將一步一步地去探索EventBus那些好玩又高級(jí)的用法。主要有
- Thread Mode(線程模式)
- Configuration(配置)
- Sticky Events(粘性事件)
- Priorities and Event Cancellation(優(yōu)先級(jí)和取消事件傳遞)
- Subscriber Index(訂閱者索引)
- AsyncExecutor(異步執(zhí)行器)
話不多說(shuō)糜值,馬上開(kāi)始
高級(jí)用法
Thread Mode
還記得上一篇中个榕,訂閱方法的@Subscribe中包含的信息
@Subscribe(threadMode = ThreadMode.MAIN)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
mTextView.setText("陳奕迅只有一個(gè)");
通過(guò)指定threadMode來(lái)確定訂閱方法在哪個(gè)線程執(zhí)行凯旭,ThreadMode.MAIN只是其中一種概耻,看一下其他的
public enum ThreadMode {
/**
* Subscriber will be called in the same thread, which is posting the event. This is the default. Event delivery
* implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for
* simple tasks that are known to complete is a very short time without requiring the main thread. Event handlers
* using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
*/
POSTING,
/**
* Subscriber will be called in Android's main thread (sometimes referred to as UI thread). If the posting thread is
* the main thread, event handler methods will be called directly. Event handlers using this mode must return
* quickly to avoid blocking the main thread.
*/
MAIN,
/**
* Subscriber will be called in a background thread. If posting thread is not the main thread, event handler methods
* will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single
* background thread, that will deliver all its events sequentially. Event handlers using this mode should try to
* return quickly to avoid blocking the background thread.
*/
BACKGROUND,
/**
* Event handler methods are called in a separate thread. This is always independent from the posting thread and the
* main thread. Posting events never wait for event handler methods using this mode. Event handler methods should
* use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number
* of long running asynchronous handler methods at the same time to limit the number of concurrent threads. EventBus
* uses a thread pool to efficiently reuse threads from completed asynchronous event handler notifications.
*/
ASYNC
}
注釋一目了然,總結(jié)一下四種模式的應(yīng)用場(chǎng)景
- POSTING:事件發(fā)布在什么線程罐呼,就在什么線程訂閱鞠柄。
- MAIN:無(wú)論事件在什么線程發(fā)布,都在主線程訂閱嫉柴。
- BACKGROUND:如果發(fā)布的線程不是主線程厌杜,則在該線程訂閱,如果是主線程计螺,則使用一個(gè)單獨(dú)的后臺(tái)線程訂閱夯尽。
- ASYNC:用線程池線程訂閱。
比如
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
mTextView.setText("陳奕迅只有一個(gè)");
}
這樣我們就是在后臺(tái)中訂閱事件登馒,但由于更新UI只能在主線程中匙握,所以會(huì)出現(xiàn)以下異常
Could not dispatch event: class com.charmingwong.androidtest.UpdateUIEvent to subscribing class class com.charmingwong.androidtest.MainActivity
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Configuration
我們獲取EventBus實(shí)例的默認(rèn)方式是
EventBus.getDefault().register(this);
這種方式的獲取到的EventBus的屬性都是默認(rèn)的,有時(shí)候并不能滿足我們的要求陈轿,這時(shí)候我們可以通過(guò)EventBusBuilder來(lái)個(gè)性化配置EventBus的屬性
EventBus eventBus = EventBus.builder().eventInheritance(true)
.ignoreGeneratedIndex(false)
.logNoSubscriberMessages(true)
.logSubscriberExceptions(false)
.sendNoSubscriberEvent(true)
.sendSubscriberExceptionEvent(true)
.throwSubscriberException(true)
.strictMethodVerification(true)
.build();
eventBus.register(this);
使用類(lèi)似于以上這種Builder模式圈纺,來(lái)達(dá)到個(gè)性化配置的效果秦忿,builder()返回的是EventBusBuilder,來(lái)看一下EventBusBuilder各項(xiàng)配置的含義
// 創(chuàng)建默認(rèn)的EventBus對(duì)象赠堵,相當(dāng)于EventBus.getDefault()小渊。
EventBus installDefaultEventBus():
// 添加由EventBus“注釋預(yù)處理器生成的索引
EventBuilder addIndex(SubscriberInfoIndex index):
// 默認(rèn)情況下,EventBus認(rèn)為事件類(lèi)有層次結(jié)構(gòu)(訂戶超類(lèi)將被通知)
EventBuilder eventInheritance(boolean eventInheritance):
// 定義一個(gè)線程池用于處理后臺(tái)線程和異步線程分發(fā)事件
EventBuilder executorService(java.util.concurrent.ExecutorService executorService):
// 設(shè)置忽略訂閱索引茫叭,即使事件已被設(shè)置索引,默認(rèn)為false
EventBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex):
// 打印沒(méi)有訂閱消息半等,默認(rèn)為true
EventBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages):
// 打印訂閱異常揍愁,默認(rèn)true
EventBuilder logSubscriberExceptions(boolean logSubscriberExceptions):
// 設(shè)置發(fā)送的的事件在沒(méi)有訂閱者的情況時(shí),EventBus是否保持靜默杀饵,默認(rèn)true
EventBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent):
// 發(fā)送分發(fā)事件的異常莽囤,默認(rèn)true
EventBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent):
// 在3.0以前,接收處理事件的方法名以onEvent開(kāi)頭切距,方法名稱(chēng)驗(yàn)證避免不是以此開(kāi)頭朽缎,啟用嚴(yán)格的方法驗(yàn)證(默認(rèn):false)
EventBuilder strictMethodVerification(java.lang.Class<?> clazz)
// 如果onEvent***方法出現(xiàn)異常,是否將此異常分發(fā)給訂閱者(默認(rèn):false)
EventBuilder throwSubscriberException(boolean throwSubscriberException)
通過(guò)這種方式谜悟,我們就可以靈活地使用EventBus话肖。
Sticky Events
一般的事件發(fā)布之后,后來(lái)注冊(cè)的訂閱者無(wú)法再收到這些事件葡幸,而sticky事件發(fā)布之后最筒,EventBus會(huì)將它保存起來(lái),直到有新的同事件類(lèi)型的sticky事件發(fā)布蔚叨,才將舊的覆蓋床蜘,因此后續(xù)的訂閱者只要使用sticky模式,依然可以獲得這個(gè)sticky事件蔑水。使用方式如下:
在SecondActivity中訂閱sticky事件并注冊(cè)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注冊(cè)該頁(yè)面為訂閱者
EventBus.getDefault().register(this);
}
@Subscribe(sticky = true)
public void onMyStickyEvent(MyStickyEvent myStickyEvent) {
mTextView.setText("有人問(wèn)你他是不是陳奕迅");
}
在MainActivity中發(fā)布事件
@Override
public void onClick(View v) {
// 發(fā)布黏性事件
EventBus.getDefault().postSticky(new MyStickyEvent());
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
看一下運(yùn)行效果:
MainActivity啟動(dòng)在前邢锯,發(fā)布sticky事件,SecondActivity再啟動(dòng)搀别,注冊(cè)成為訂閱者之后依然能夠收到sticky事件丹擎,成功說(shuō)明sticky的作用,也驗(yàn)證了以上說(shuō)法领曼。發(fā)布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
}
Priorities and Event Cancellation
這里用一個(gè)例子驗(yàn)證優(yōu)先級(jí)關(guān)系
// MainActivity
@Subscribe(priority = 2)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
Log.d(TAG, "onUpdateUIEvent: priority = 2");
}
// SecondActivity
@Subscribe(priority = 1)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
Log.d(TAG, "onUpdateUIEvent: priority = 1");
}
兩個(gè)Activity中都訂閱相同的事件類(lèi)型,但優(yōu)先級(jí)不同庶骄,在SecondActivity發(fā)布事件
// 發(fā)布事件
EventBus.getDefault().post(new UpdateUIEvent());
看運(yùn)行效果毁渗,日志:
D/MainActivity: onUpdateUIEvent: priority = 2
D/SecondActivity: onUpdateUIEvent: priority = 1
再將priority調(diào)換一下
// MainActivity
@Subscribe(priority = 1)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
Log.d(TAG, "onUpdateUIEvent: priority = 1");
}
// SecondActivity
@Subscribe(priority = 2)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
Log.d(TAG, "onUpdateUIEvent: priority = 2");
}
運(yùn)行,看日志:
D/SecondActivity: onUpdateUIEvent: priority = 2
D/MainActivity: onUpdateUIEvent: priority = 1
可見(jiàn)priority決定收到事件的先后順序单刁,priority越大灸异,越先收到事件府适。先收到的訂閱者還可以攔截該事件,并取消繼續(xù)傳遞肺樟,后續(xù)優(yōu)先級(jí)低訂閱者就不會(huì)再收到該事件檐春。我們可以這樣設(shè)置取消事件繼續(xù)傳遞:
@Subscribe(priority = 2)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
Log.d(TAG, "onUpdateUIEvent: priority = 2");
EventBus.getDefault().cancelEventDelivery(updateUIEvent);
}
運(yùn)行,看日志:
D/SecondActivity: onUpdateUIEvent: priority = 2
果然么伯,priority=1的訂閱者就沒(méi)有收到該事件疟暖,這就是EventBus的取消事件繼續(xù)傳遞功能。
Subscriber Index
看完官方文檔田柔,我們知道Subscriber Index是EventBus 3上的新技術(shù)俐巴,所以這里也建議還沒(méi)學(xué)習(xí)過(guò)EventBus的可以跳過(guò)2.X之前的版本直接學(xué)習(xí)最新版本。
關(guān)于EventBus的Subscriber Index技術(shù)的特點(diǎn)硬爆,翻譯一下官方解釋?zhuān)?/p>
It is an optional optimization to speed up initial subscriber registration.
Subscriber Index是一個(gè)可選的優(yōu)化技術(shù)欣舵,用來(lái)加速初始化訂閱者注冊(cè)。
The subscriber index can be created during build time using the EventBus annotation processor. While it is not required to use an index, it is recommended on Android for best performance.
Subscriber Index在編譯時(shí)使用EventBus注解處理器創(chuàng)建缀磕,雖然沒(méi)有規(guī)定必須使用它缘圈,但是官方推薦使用這種方式,因?yàn)樗贏ndroid上有著最佳的性能袜蚕。
使用方式:在gradle做如下配置
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
}
}
}
}
dependencies {
compile 'org.greenrobot:eventbus:3.1.1'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
如果以上方法不生效糟把,還可以使用android-apt Gradle plugin
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'org.greenrobot:eventbus:3.1.1'
apt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
apt {
arguments {
eventBusIndex "com.example.myapp.MyEventBusIndex"
}
}
配置完了之后,我們就可以在代碼中添加索引了
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
除了application廷没,還可以在library中使用索引
EventBus eventBus = EventBus.builder()
.addIndex(new MyEventBusAppIndex())
.addIndex(new MyEventBusLibIndex()).build();
好了糊饱,這就是索引的使用方式。
AsyncExecutor
先看個(gè)例子
AsyncExecutor.create().execute(
new AsyncExecutor.RunnableEx() {
@Override
public void run() throws Exception {
// No need to catch any Exception (here: LoginException)
prepare();
EventBus.getDefault().postSticky(new MyStickyEvent());
}
}
);
private void prepare() throws Exception {
throw new Exception("prepare failed");
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
Log.d(TAG, "onUpdateUIEvent: ");
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleFailureEvent(ThrowableFailureEvent event) {
Log.d(TAG, "handleFailureEvent: " + event.getThrowable().getMessage());
通過(guò)這樣的方式颠黎,如果相關(guān)代碼出現(xiàn)了異常另锋,則會(huì)將異常封裝成ThrowableFailureEvent,自動(dòng)發(fā)布出去狭归,只要訂閱者定義了接收ThrowableFailureEvent的方法夭坪,就可以拿到異常信息,后續(xù)的UpdateUIEvent也不會(huì)再發(fā)布过椎,如果沒(méi)有出現(xiàn)異常室梅,則正常發(fā)布事件。
運(yùn)行疚宇,看日志:
D/SecondActivity: handleFailureEvent: prepare failed
果然亡鼠,只有handleFailureEvent(ThrowableFailureEvent event)收到了異常事件。這種寫(xiě)法的好處是能把異常信息交給訂閱者敷待,讓訂閱者根據(jù)情況處理间涵。
總結(jié)
好了,以上就是EventBus相對(duì)高級(jí)一點(diǎn)的用法榜揖,除了以上提到的勾哩,還有一些別的特性抗蠢,具體請(qǐng)看官方文檔,如果有不明白的地方思劳,也一定要看官方文檔迅矛,這個(gè)非常重要,附上傳送門(mén):
戳我潜叛,看EventBus官方文檔秽褒!
下一篇著重于分析EventBus的源碼,了解其實(shí)現(xiàn)原理威兜。