EventBus 的使用

前言

EventBus 是一個很是優秀的 Android 事件總線框架,能夠用來簡化組件間通訊與數據傳輸,好比 Activity 之間、Fragment 之間、子線程與 UI 線程間等等狀況。它基於發佈/訂閱模式,實現業務代碼的解耦,提升了開發的效率。java

最近在作需求時也使用到了 EventBus 3.0,在使用粘性事件時也遇到一些問題,這篇文章來簡單總結一下 EventBus 的使用。android

基本概念

EventBus 主要由三個元素構成:bash

  • 事件(Event)
  • 發佈者(Publisher)
  • 接收者(Subscriber)

事件(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 中訂閱事件,須要在生命週期回調函數中作這步。

  • Activity:在 onCreate 中註冊,在 onDestory 中註銷。
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);
}
複製代碼
  • Fragment:在 onCreateView 中註冊,在 onDestoryView 中註銷。
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的內容。

效果如圖:

img1

線程模型

這部分來介紹如下 EventBus 的幾種線程模型,線程模型是用來指定訂閱者在什麼線程中處理這個事件。

有以下幾種線程模型:

  1. ThreadMode.POSTING

默認的線程模型,訂閱者會在發佈事件的同一個線程裏被調用。事件傳遞是同步完成的,事件發佈後,全部的訂閱者都會接收到事件,由於沒有切換線程,因此開銷最小。若是是簡單的任務,這種模型最爲推薦。

@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessage(MessageEvent event) {
    log(event.message);
}
複製代碼
  1. ThreadMode: MAIN

訂閱者將在 Android 的主線程(UI 線程)中調用。 若是發佈事件的線程是主線程,那麼就和 POSTING 是同樣的。 使用這個模式的事件處理不能有耗時操做,必須快速返回,不然容易阻塞主線程,形成 ANR。

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
    textText.setText(event.message);
}
複製代碼
  1. ThreadMode: MAIN_ORDERED

這個模型下,訂閱者一樣會在 Android 主線程被調用。與 MAIN 不一樣的是,訂閱者處理接收該事件是串行的,第二個訂閱者須要在第一個訂閱者處理完後纔會接收到事件,因此被稱爲 ordered。這個模型一樣要避免阻塞主線程。

@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void onMessage(MessageEvent event) {
    textText.setText(event.message);
}
複製代碼
  1. ThreadMode: BACKGROUND

從名字能夠看出,這個模型是在後臺處理事件。若是在子線程中發佈事件,則會在那個子線程中調用訂閱者,進行處理;若是在主線程中發佈事件,則會新建立一個子線程,而後在子線程中處理。

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
    saveToDisk(event.message);
}
複製代碼
  1. ThreadMode: ASYNC

在這個模型下,老是會在一個新的線程中取處理事件,這個線程獨立於發佈線程和主線程。若是在事件處理方法中有耗時操做,如網絡請求等等,採用這種模型。 EventBus 使用了線程池來重用線程,節省開銷。

@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
    backend.send(event.message);
}
複製代碼

粘性事件(StickyEvent)

普通事件必需要訂閱者先註冊,而後發送事件,才能被訂閱者接收到。有的狀況下,咱們但願即便先發送事件,再註冊,訂閱者也能接收到。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();
    }
}
複製代碼

效果演示:

img2

代碼的邏輯很是簡單,MainActivity 中沒有在 onCreate 中註冊 EventBus,而是將它放在了一個按鈕的點擊事件中。

演示中一共發送三次事件:

  1. 未註冊時,從 SecondActivity 發送普通事件,發現 TextView 文字沒有變化;
  2. 未註冊時,從 SecondActivity 發送 Sticky 事件,回到 MainActivity 中點擊註冊 EventBus,發現這時文字變化了。
  3. 註冊後,從 SecondActivity 發送普通事件,發現文字再次變化。

這就是 Sticky 事件的做用,先發送事件,再註冊訂閱者,也能夠接受到事件。

使用注意

發送的粘性事件會保存在一個 map 中,這樣才能實現後訂閱也能接收到的功能,同時會保留最後一個粘性事件。對於某些只處理一次的事件,會形成重複處理最後一個粘性事件的狀況,有時這是不符合預期的,須要在處理完粘性事件後手動將其刪除,使用 removeStickyEvent 方法。

StringEvent stickyEvent = EventBus.getDefault().getStickyEvent(StringEvent.class);
// 判斷此粘性事件是否存在
if(stickyEvent != null) {
    // 若粘性事件存在,將其刪除
    EventBus.getDefault().removeStickyEvent(stickyEvent);
}
複製代碼

ProGuard 混淆規則

-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);
}
複製代碼
相關文章
相關標籤/搜索