EventBus 3.0初探: 入門使用及其使用 完全解析

前言

EventBus是greenrobot在Android平臺發布的一款以訂閱——發布模式為核心的開源庫。EventBus翻譯過來是事件總線的意思,可以這樣理解:一個個事件(event)發送到總線上,然后EventBus根據已注冊的訂閱者(subscribers)來匹配相應的事件,進而把事件傳遞給訂閱者,這也是觀察者模式的一個最佳實踐。

那么EventBus可以用到什么地方呢?我們平時開發的時候,當遇到Activity與Activity、Activity與Fragment之間的通信,往往采用Intent,又或者線程之間使用Handler進行通信,這樣代碼難免會復雜許多,而使用EventBus能極大簡化兩個組件之間的通信問題,而且效率極高,而EventBus升級到3.0版本后,開發者能夠自定義訂閱方法的名字,而沒必要規定以“onEventXX”開頭的方法了,這樣也自由化了很多,而且支持了粘性事件的分發等,因此學會使用EventBus3.0對我們的開發有極大的好處。

官網

EventBus GitHub地址
在Android Studio中添加如下依賴:

compile 'org.greenrobot:eventbus:3.0.0'

使用

在準備好EventBus后,我們通過一個例子來展示它的使用方法。實現的例子很簡單,主要是在Activity1中打開Activity2,而Activity2中通過點擊按鈕來使Activity1中的TextView顯示內容得到更改,而顯示的內容由Activity2指定。這便構成了一個典型的Activity與Activity之間通信的場景,如果用我們之前的做法可以采用BroadcastReceiver來實現兩者的通信,但現在我們使用EventBus,只需要幾行代碼就能實現了。

首先,我們準備兩個布局文件,分別是MainActivity和SecondActivity的:
activity_main.xml:

<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" >

    <TextView
        android:id="@+id/tv_text"
        android:textSize="20sp"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="點擊打開新的Activity"
        android:id="@+id/secondActivityBtn"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="75dp" />

</RelativeLayout>

second_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <Button
        android:id="@+id/sendMessageBtn"
        android:text="發送消息"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
</LinearLayout>

接著,我們利用EventBus來實現上述需求。

Step 1.創建事件實體類

所謂的事件實體類,就是傳遞的事件,一個組件向另一個組件發送的信息可以儲存在一個類中,該類就是一個事件,會被EventBus發送給訂閱者。新建MessageEvent.java:

public class MessageEvent {

    private String message;

    public MessageEvent(String message){
        this.message = message;
    }

    public String getMessage(){
        return message;
    }
}

這里創建的事件非常簡單,只是為了示例需要。

Step 2.向EventBus注冊,成為訂閱者以及解除注冊

通過以下代碼:

EventBus.getDefault().register(this);

即可將當前類注冊,成為訂閱者,即對應觀察者模式的“觀察者”,一旦有事件發送過來,該觀察者就會接收到匹配的事件。通常,在類的初始化時便進行注冊,如果是Activity則在onCreate()方法內進行注冊。

當訂閱者不再需要接受事件的時候,我們需要解除注冊,釋放內存:

EventBus.getDefault().unregister(this);

Step 3.聲明訂閱方法

回想觀察者模式,觀察者有著一個update()方法,在接收到事件的時候會調用該update()方法,這個方法就是一個訂閱方法。在EventBus 3.0中,聲明一個訂閱方法需要用到@Subscribe注解,因此在訂閱者類中添加一個有著@Subscribe注解的方法即可,方法名字可自定義,而且必須是public權限,其方法參數有且只能有一個,另外類型必須為第一步定義好的事件類型(比如上面的MessageEvent),如下所示:

@Subscribe 
public void onEvent(AnyEventType event) {
    /* Do something */
}

完整的MainActivity.java文件如下所示:

public class MainActivity extends Activity {

    private TextView textView;
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //注冊成為訂閱者
        EventBus.getDefault().register(this);
        textView = (TextView) findViewById(R.id.tv_text);
        button = (Button) findViewById(R.id.secondActivityBtn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
    
    //訂閱方法,當接收到事件的時候,會調用該方法
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(MessageEvent messageEvent){
        Log.d("cylog","receive it");
        textView.setText(messageEvent.getMessage());
        Toast.makeText(MainActivity.this, messageEvent.getMessage(), Toast.LENGTH_SHORT).show();
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除注冊
        EventBus.getDefault().unregister(this);
    }
}

Step 4.發送事件

與觀察者模式對應的,當有事件發生,需要通知觀察者的時候,被觀察者會調用notifyObservers()方法來通知所有已經注冊的觀察者,在EventBus中,對觀察者模式底層進行了封裝,我們只需要調用以下代碼就能把事件發送出去:

EventBus.getDefault().post(EventType eventType);

上述EventType就是第一步定義的事件類型。
SecondActivity.java代碼如下所示:

public class SecondActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_main);
        Button button = (Button) findViewById(R.id.sendMessageBtn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new MessageEvent("Hello !....."));
            }
        });
    }
}

由此可見,經過以上簡單的四個步驟,就能實現事件在組件之間的傳遞了,這是EventBus的便捷性。

進一步認識@Subscribe注解

我們回頭看看上面的訂閱方法,添加了@Subscribe注解,該注解標識了當前方法為訂閱方法,我們可以看到上面我們還給該注解賦值(threadMode = ThreadMode.MAIN),那么,這個代表了什么意思呢?首先,我們看看@Subscribe的類文件:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * If true, delivers the most recent sticky event (posted with
     * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
     */
    boolean sticky() default false;

    /** Subscriber priority to influence the order of event delivery.
     * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
     * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
     * delivery among subscribers with different {@link ThreadMode}s! */
    int priority() default 0;
}

該注解內部有三個成員,分別是threadMode、sticky、priority。threadMode代表訂閱方法所運行的線程,sticky代表是否是粘性事件,priority代表優先級。給這個三個成員賦不同的值,能使得訂閱方法有著不同的效果。

1.ThreadMode是一個枚舉類型,有著以下幾個類型:

public enum ThreadMode {
    
    POSTING,
    
    MAIN,
    
    BACKGROUND,

    ASYNC
}

POSTING:表示訂閱方法運行在發送事件的線程。
MAIN:表示訂閱方法運行在UI線程,由于UI線程不能阻塞,因此當使用MAIN的時候,訂閱方法不應該耗時過長。
BACKGROUND:表示訂閱方法運行在后臺線程,如果發送的事件線程不是UI線程,那么就使用該線程;如果發送事件的線程是UI線程,那么新建一個后臺線程來調用訂閱方法。
ASYNC:訂閱方法與發送事件始終不在同一個線程,即訂閱方法始終會使用新的線程來運行。
ThreadMode默認是使用POSTING的,如果需要更改設置,可以在添加注解的時候同時為threadMode賦值。

2.priority 優先級

設置該優先級的目的是,當一個事件有多個訂閱者的時候,優先級高的會優先接收到事件。

3.sticky 粘性事件

關于粘性事件,可以參考Android的廣播機制,其中有一個粘性廣播,粘性廣播的意思是:該廣播發送后,會保存在內存中,如果后來有注冊的Receiver與之匹配,那么該Receiver便會接收到該廣播。那么粘性事件同理,在注冊之前便把事件發生出去,等到注冊之后便會收到最近發送的粘性事件(必須匹配)。注意:只會接收到最近發送的一次粘性事件,之前的會接受不到。為了測試,我們來建立一個小demo:
我們在Activity中添加四個按鈕,布局很簡單不給出了,前三個按鈕分別發送三個粘性事件,最后一個按鈕進行注冊,代碼如下:

public class SecondActivity extends Activity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_main);
        Button button1 = (Button) findViewById(R.id.sendStickyMessageBtn1);
        Button button2 = (Button) findViewById(R.id.sendStickyMessageBtn2);
        Button button3 = (Button) findViewById(R.id.sendStickyMessageBtn3);
        Button button4 = (Button) findViewById(R.id.sendRegisterBtn);
        button1.setOnClickListener(this);
        button2.setOnClickListener(this);
        button3.setOnClickListener(this);
        button4.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.sendStickyMessageBtn1:
                EventBus.getDefault().postSticky(new MessageEvent("粘性事件1"));
                Log.d("cylog","發送粘性事件1...");
                break;
            case R.id.sendStickyMessageBtn2:
                EventBus.getDefault().postSticky(new MessageEvent("粘性事件2"));
                Log.d("cylog", "發送粘性事件2...");
                break;
            case R.id.sendStickyMessageBtn3:
                EventBus.getDefault().postSticky(new MessageEvent("粘性事件3"));
                Log.d("cylog", "發送粘性事件3...");
                break;
            case R.id.sendRegisterBtn:
                Log.d("cylog", "注冊成為訂閱者...");
                EventBus.getDefault().register(this);

                break;
        }
    }

    @Subscribe(sticky = true)
    public void onEvent(MessageEvent messageEvent){
        Log.d("cylog","接受到了來自EventBus的事件:"+messageEvent.getMessage());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
}

我們依次按下這4個按鈕,按先后次序發送粘性事件1、2、3以及注冊訂閱者,我們可以看到下面的結果:


結果.jpg

那么很明顯,只會接受到最后發送的粘性事件,在此之前的事件都接收不到。

本篇文章到此為止,系統地介紹了EventBus 3.0的詳細用法,其中也穿插了觀察者模式的幾個知識點,如果對觀察者模式不熟悉的讀者可以查看我之前發的一篇文章。至于EventBus其背后的原理以及源碼將會在下一篇文章中繼續為大家詳細解析,謝謝大家的閱讀~

更多閱讀
學習、探究Java設計模式——觀察者模式
EventBus 3.0進階:源碼及其設計模式 完全解析

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容