EventBus 是一個很是優秀的 Android 事件總線框架,能夠用來簡化組件間通訊與數據傳輸,好比 Activity 之間、Fragment 之間、子線程與 UI 線程間等等狀況。它基於發佈/訂閱模式,實現業務代碼的解耦,提升了開發的效率。java
最近在作需求時也使用到了 EventBus 3.0,在使用粘性事件時也遇到一些問題,這篇文章來簡單總結一下 EventBus 的使用。android
EventBus 主要由三個元素構成:bash
事件(Event)能夠是任何類型。能夠分爲普通事件和粘性事件(StickyEvent),粘性事件文章後面會單獨介紹。網絡
事件發佈者(Publisher),能夠在任意線程的任意位置調用 post 方法送事件。框架
事件訂閱者/事件接收者(Subscriber),在須要接收事件的位置(一般是 Activity、Fragment,隨業務決定),建立一個方法接收事件進行處理,EventBus 3.0 以後方法名能夠任意指定,不過須要添加 @Subscribe
註解,註解中能夠指定線程模型、是否支持粘性事件、優先級。ide
能夠經過如下幾個步驟來快速上手使用 EventBus:函數
0. 添加依賴post
在 gradle 中添加對 EventBus 的依賴:gradle
implementation 'org.greenrobot:eventbus:3.1.1'
複製代碼
1. 定義一個事件ui
通常來講咱們根據業務來定義事件,爲了意思明確再加上 Event,代表這是個 EventBus 的事件,好比 LoginEvent、LogoutEvent,分別對應在登錄後、登出後發送的事件。
咱們這裏能夠定義一個攜帶一個 String 變量的 StringEvent:
class StringEvent{
public String msg;
public StringEvent(String msg){
this.msg = msg;
}
}
複製代碼
2. 註冊、註銷 EventBus
訂閱者須要在總線中註冊/註銷。只有註冊後,訂閱者才能正常接收到事件。一般咱們都在 Activity、Fragment 中訂閱事件,須要在生命週期回調函數中作這步。
Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
}
複製代碼
@Override protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
複製代碼
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
EventBus.getDefault().register(this);
return super.onCreateView(inflater, container, savedInstanceState);
}
複製代碼
@Override public void onDestroyView() {
super.onDestroyView();
EventBus.getDefault().unregister(this);
}
複製代碼
EventBus 的官方文檔中推薦在 onStart 和 onStop 中進行註冊和註銷,提到這個時期對絕大多數狀況適用。在開發過程當中也會遇到在 onStart 註冊偏晚形成接收不到事件的情形,因此也能夠把註冊時機稍微提早。
3. 發送事件
發送事件很是簡單,只須要調用一個 post 方法便可。
EventBus.getDefault().post(new StringEvent("hello"));
複製代碼
4. 處理事件
在 Activity 或 Fragment 中定義一個方法,方法名能夠任意指定,參數爲要接收的 Event 對象,須要加上 @Subscribe
註解。註解中能夠添加線程模型、是否支持粘性事件、優先級屬性。其中線程模型是必需的,默認值爲 POSTING,線程模型的概念在示例以後進行介紹。
@Subscribe //這裏沒有指定線程模型,使用默認值
public void onStringEvent(StringEvent event) {
//業務邏輯
}
複製代碼
完整示例代碼
下面貼一個簡單的示例代碼,結合演示效果應該比較好理解。
MainActivity
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
mTextView = findViewById(R.id.textview);
new Thread(new Runnable() {
@Override public void run() {
try {
Thread.sleep(3000);
EventBus.getDefault().post(new StringEvent("我接收到事件了"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onStringEvent(StringEvent event) {
mTextView.setText(event.text);
}
@Override protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}
複製代碼
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="初始文案"
android:layout_centerInParent="true"
android:textSize="20sp"
android:id="@+id/textview"/>
</RelativeLayout>
複製代碼
代碼的邏輯很是簡單,在 MainActivity 中有一個TextView,有個初始文案,建立了一個子線程,在3s後發送一個事件,接受到事件後修改TextView的內容。
效果如圖:
這部分來介紹如下 EventBus 的幾種線程模型,線程模型是用來指定訂閱者在什麼線程中處理這個事件。
有以下幾種線程模型:
默認的線程模型,訂閱者會在發佈事件的同一個線程裏被調用。事件傳遞是同步完成的,事件發佈後,全部的訂閱者都會接收到事件,由於沒有切換線程,因此開銷最小。若是是簡單的任務,這種模型最爲推薦。
@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessage(MessageEvent event) {
log(event.message);
}
複製代碼
訂閱者將在 Android 的主線程(UI 線程)中調用。 若是發佈事件的線程是主線程,那麼就和 POSTING 是同樣的。 使用這個模式的事件處理不能有耗時操做,必須快速返回,不然容易阻塞主線程,形成 ANR。
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
textText.setText(event.message);
}
複製代碼
這個模型下,訂閱者一樣會在 Android 主線程被調用。與 MAIN 不一樣的是,訂閱者處理接收該事件是串行的,第二個訂閱者須要在第一個訂閱者處理完後纔會接收到事件,因此被稱爲 ordered。這個模型一樣要避免阻塞主線程。
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void onMessage(MessageEvent event) {
textText.setText(event.message);
}
複製代碼
從名字能夠看出,這個模型是在後臺處理事件。若是在子線程中發佈事件,則會在那個子線程中調用訂閱者,進行處理;若是在主線程中發佈事件,則會新建立一個子線程,而後在子線程中處理。
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
saveToDisk(event.message);
}
複製代碼
在這個模型下,老是會在一個新的線程中取處理事件,這個線程獨立於發佈線程和主線程。若是在事件處理方法中有耗時操做,如網絡請求等等,採用這種模型。 EventBus 使用了線程池來重用線程,節省開銷。
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
backend.send(event.message);
}
複製代碼
普通事件必需要訂閱者先註冊,而後發送事件,才能被訂閱者接收到。有的狀況下,咱們但願即便先發送事件,再註冊,訂閱者也能接收到。EventBus 提供了粘性事件來知足這種情形。
在發佈事件時,改用 postSticky 方法來發送一個粘性事件。
EventBus.getDefault().postSticky(new StringEvent("Hello World!"));
複製代碼
而後在訂閱者接收事件方法的 @Subscribe 註解中加上sticky=true
,表示支持接收粘性事件。
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onStringEvent(StringEvent event) {
mTextView.setText(event.message);
}
複製代碼
下面一樣寫一個比較簡單的示例來演示一下粘性事件的用法:
MainActivity:
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportActionBar().setTitle("MainActivity");
mTextView = findViewById(R.id.textview);
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onStringEvent(StringEvent event) {
mTextView.setText(event.text);
}
@Override protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
public void register(View view) {
EventBus.getDefault().register(this);
}
public void toSecondActivity(View view) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
}
複製代碼
SecondActivity
public class SecondActivity extends AppCompatActivity {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
getSupportActionBar().setTitle("SecondActivity");
}
public void sendEvent(View view){
EventBus.getDefault().post(new StringEvent("OWEN"));
finish();
}
public void sendStickyEvent(View view){
EventBus.getDefault().postSticky(new StringEvent("Sticky OWEN"));
finish();
}
}
複製代碼
效果演示:
代碼的邏輯很是簡單,MainActivity 中沒有在 onCreate 中註冊 EventBus,而是將它放在了一個按鈕的點擊事件中。
演示中一共發送三次事件:
這就是 Sticky 事件的做用,先發送事件,再註冊訂閱者,也能夠接受到事件。
使用注意
發送的粘性事件會保存在一個 map 中,這樣才能實現後訂閱也能接收到的功能,同時會保留最後一個粘性事件。對於某些只處理一次的事件,會形成重複處理最後一個粘性事件的狀況,有時這是不符合預期的,須要在處理完粘性事件後手動將其刪除,使用 removeStickyEvent
方法。
StringEvent stickyEvent = EventBus.getDefault().getStickyEvent(StringEvent.class);
// 判斷此粘性事件是否存在
if(stickyEvent != null) {
// 若粘性事件存在,將其刪除
EventBus.getDefault().removeStickyEvent(stickyEvent);
}
複製代碼
-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);
}
複製代碼