前言:EventBus出來已經有一段時間了,github上面也有很多開源項目中使用了EventBus。所以抽空學習順便整理了一下。目前EventBus最新版本是3.0,所以本文是基于EventBus3.0的。
相關文章
EventBus使用詳解
EventBus源碼解析
概述
EventBus是針一款對Android的發布/訂閱事件總線。它可以讓我們很輕松的實現在Android各個組件之間傳遞消息,并且代碼的可讀性更好,耦合度更低。
如何使用
(1)首先需要定義一個消息類,該類可以不繼承任何基類也不需要實現任何接口。如:
public class MessageEvent {
......
}
(2)在需要訂閱事件的地方注冊事件
EventBus.getDefault().register(this);
(3)產生事件,即發送消息
EventBus.getDefault().post(messageEvent);
(4)處理消息
@Subscribe(threadMode = ThreadMode.PostThread)
public void XXX(MessageEvent messageEvent) {
...
}
在3.0之前,EventBus還沒有使用注解方式。消息處理的方法也只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,分別代表四種線程模型。而在3.0之后,消息處理的方法可以隨便取名,但是需要添加一個注解@Subscribe,并且要指定線程模型(默認為PostThread),四種線程模型,下面會講到。
注意,事件處理函數的訪問權限必須為public,否則會報異常。
(5)取消消息訂閱
EventBus.getDefault().unregister(this);
有何優點
采用消息發布/訂閱的一個很大的優點就是代碼的簡潔性,并且能夠有效地降低消息發布者和訂閱者之間的耦合度。
舉個例子,比如有兩個界面,ActivityA和ActivityB,從ActivityA界面跳轉到ActivityB界面后,ActivityB要給ActivityA發送一個消息,ActivityA收到消息后在界面上顯示出來。我們最先想到的方法就是使用廣播,使用廣播實現此需求的代碼如下:
首先需要在ActivityA中定義一個廣播接收器:
public class MessageBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
mMessageView.setText("Message from SecondActivity:" + intent.getStringExtra("message"));
}
}
還需要在onCreate()方法中注冊廣播接收器:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注冊事件
EventBus.getDefault().register(this);
//注冊廣播
IntentFilter intentFilter = new IntentFilter("message_broadcast");
mBroadcastReceiver = new MessageBroadcastReceiver();
registerReceiver(mBroadcastReceiver, intentFilter);
......
}
然后在onDestory()方法中取消注冊廣播接收器:
@Override
protected void onDestroy() {
super.onDestroy();
......
//取消廣播注冊
unregisterReceiver(mBroadcastReceiver);
}
最后我們需要在ActivityB界面中發送廣播消息:
findViewById(R.id.send_broadcast).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String message = mMessageET.getText().toString();
if(TextUtils.isEmpty(message)) {
message = "defaule message";
}
Intent intent = new Intent();
intent.setAction("message_broadcast");
intent.putExtra("message", message);
sendBroadcast(intent);
}
});
看著上面的實現代碼,感覺也沒什么不妥,挺好的!下面對比看下使用EventBus如何實現。
根據文章最前面所講的EventBus使用步驟,首先我們需要定義一個消息事件類:
public class MessageEvent {
private String message;
public MessageEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
在ActivityA界面中我們首先需要注冊訂閱事件:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注冊事件
EventBus.getDefault().register(this);
......
}
然后在onDestory()方法中取消訂閱:
@Override
protected void onDestroy() {
super.onDestroy();
//取消事件注冊
EventBus.getDefault().unregister(this);
}
當然還要定義一個消息處理的方法:
@Subscribe(threadMode = ThreadMode.MainThread)
public void onShowMessageEvent(MessageEvent messageEvent) {
mMessageView.setText("Message from SecondActivity:" + messageEvent.getMessage());
}
至此,消息訂閱者我們已經定義好了,我們還需要在ActivityB中發布消息:
findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String message = mMessageET.getText().toString();
if(TextUtils.isEmpty(message)) {
message = "defaule message";
}
EventBus.getDefault().post(new MessageEvent(message));
}
});
對比代碼一看,有人會說了,這尼瑪有什么區別嘛!說好的簡潔呢?哥們,別著急嘛!我這里只是舉了個簡單的例子,僅僅從該例子來看,EventBus的優勢沒有體現出來。現在我將需求稍微改一下,ActivityA收到消息后,需要從網絡服務器獲取數據并將數據展示出來。如果使用廣播,ActivityA中廣播接收器代碼應該這么寫:
public class MessageBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
new Thread(new Runnable() {
@Override
public void run() {
//從服務器上獲取數據
......
runOnUiThread(new Runnable() {
@Override
public void run() {
//將獲取的數據展示在界面上
......
}
});
}
}).start();
}
}
看到這段代碼,不知道你何感想,反正我是看著很不爽,嵌套層次太多,完全違反了Clean Code的原則。那使用EventBus來實現又是什么樣呢?我們看一下。
@Subscribe(threadMode = ThreadMode.BackgroundThread)
public void onGetDataEvent(MessageEvent messageEvent) {
//從服務器上獲取數據
......
EventBus.getDefault().post(new ShowMessageEvent());
}
@Subscribe(threadMode = ThreadMode.MainThread)
public void onShowDataEvent(ShowMessageEvent showMessageEvent) {
//將獲取的數據展示在界面上
......
}
對比一下以上兩段代碼就能很明顯的感覺到EventBus的優勢,代碼簡潔、層次清晰,大大提高了代碼的可讀性和可維護性。我這只是簡單的加了一個小需求而已,隨著業務越來越復雜,使用EventBus的優勢愈加明顯。
常用API介紹
線程模型
在EventBus的事件處理函數中需要指定線程模型,即指定事件處理函數運行所在的想線程。在上面我們已經接觸到了EventBus的四種線程模型。那他們有什么區別呢?
在EventBus中的觀察者通常有四種線程模型,分別是PostThread(默認)、MainThread、BackgroundThread與Async。
- PostThread:如果使用事件處理函數指定了線程模型為PostThread,那么該事件在哪個線程發布出來的,事件處理函數就會在這個線程中運行,也就是說發布事件和接收事件在同一個線程。在線程模型為PostThread的事件處理函數中盡量避免執行耗時操作,因為它會阻塞事件的傳遞,甚至有可能會引起ANR。
- MainThread:如果使用事件處理函數指定了線程模型為MainThread,那么不論事件是在哪個線程中發布出來的,該事件處理函數都會在UI線程中執行。該方法可以用來更新UI,但是不能處理耗時操作。
- BackgroundThread:如果使用事件處理函數指定了線程模型為BackgroundThread,那么如果事件是在UI線程中發布出來的,那么該事件處理函數就會在新的線程中運行,如果事件本來就是子線程中發布出來的,那么該事件處理函數直接在發布事件的線程中執行。在此事件處理函數中禁止進行UI更新操作。
- Async:如果使用事件處理函數指定了線程模型為Async,那么無論事件在哪個線程發布,該事件處理函數都會在新建的子線程中執行。同樣,此事件處理函數中禁止進行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());
}
分別使用上面四個方法訂閱同一事件,打印他們運行所在的線程。首先我們在UI線程中發布一條MessageEvent的消息,看下日志打印結果是什么。
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());
}
});
打印結果如下:
2689-2689/com.lling.eventbusdemo E/postEvent﹕ main
2689-2689/com.lling.eventbusdemo E/PostThread﹕ main
2689-3064/com.lling.eventbusdemo E/Async﹕ pool-1-thread-1
2689-2689/com.lling.eventbusdemo E/MainThread﹕ main
2689-3065/com.lling.eventbusdemo E/BackgroundThread﹕ pool-1-thread-2
從日志打印結果可以看出,如果在UI線程中發布事件,則線程模型為PostThread的事件處理函數也執行在UI線程,與發布事件的線程一致。線程模型為Async的事件處理函數執行在名字叫做pool-1-thread-1的新的線程中。而MainThread的事件處理函數執行在UI線程,BackgroundThread的時間處理函數執行在名字叫做pool-1-thread-2的新的線程中。
我們再看看在子線程中發布一條MessageEvent的消息時,會有什么樣的結果。
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();
}
});
打印結果如下:
3468-3945/com.lling.eventbusdemo E/postEvent﹕ Thread-125
3468-3945/com.lling.eventbusdemo E/PostThread﹕ Thread-125
3468-3945/com.lling.eventbusdemo E/BackgroundThread﹕ Thread-125
3468-3946/com.lling.eventbusdemo E/Async﹕ pool-1-thread-1
3468-3468/com.lling.eventbusdemo E/MainThread﹕ main
從日志打印結果可以看出,如果在子線程中發布事件,則線程模型為PostThread的事件處理函數也執行在子線程,與發布事件的線程一致(都是Thread-125)。BackgroundThread事件模型也與發布事件在同一線程執行。Async則在一個名叫pool-1-thread-1的新線程中執行。MainThread還是在UI線程中執行。
上面一個例子充分驗證了指定不同線程模型的事件處理方法執行所在的線程。
黏性事件
除了上面講的普通事件外,EventBus還支持發送黏性事件。何為黏性事件呢?簡單講,就是在發送事件之后再訂閱該事件也能收到該事件,跟黏性廣播類似。具體用法如下:
訂閱黏性事件:
EventBus.getDefault().register(StickyModeActivity.this);
黏性事件處理函數:
@Subscribe(sticky = true)
public void XXX(MessageEvent messageEvent) {
......
}
發送黏性事件:
EventBus.getDefault().postSticky(new MessageEvent("test"));
處理消息事件以及取消訂閱和上面方式相同。
看個簡單的黏性事件的例子,為了簡單起見我這里就在一個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>
代碼很簡單,界面上三個按鈕,一個用來發送黏性事件,一個用來訂閱事件,還有一個用來取消訂閱的。首先在未訂閱的情況下點擊發送按鈕發送一個黏性事件,然后點擊訂閱,會看到日志打印結果如下:
15246-15246/com.lling.eventbusdemo E/PostThread﹕ test0
15246-15391/com.lling.eventbusdemo E/Async﹕ test0
15246-15246/com.lling.eventbusdemo E/MainThread﹕ test0
15246-15393/com.lling.eventbusdemo E/BackgroundThread﹕ test0
這就是粘性事件,能夠收到訂閱之前發送的消息。但是它只能收到最新的一次消息,比如說在未訂閱之前已經發送了多條黏性消息了,然后再訂閱只能收到最近的一條消息。這個我們可以驗證一下,我們連續點擊5次POST按鈕發送5條黏性事件,然后再點擊REGIST按鈕訂閱,打印結果如下:
6980-6980/com.lling.eventbusdemo E/PostThread﹕ test4
6980-6980/com.lling.eventbusdemo E/MainThread﹕ test4
6980-7049/com.lling.eventbusdemo E/Async﹕ test4
6980-7048/com.lling.eventbusdemo E/BackgroundThread﹕ test4
由打印結果可以看出,確實是只收到最近的一條黏性事件。
好了,EventBus的使用暫時分析到這里,例子代碼從這里獲取。下一講將講解EventBus的源碼。