EventBus3.0使用詳解

綜述

  這裏所介紹的EventBus指的是greenrobot的EventBus,它是一款針對Android的發佈/訂閱事件總線。它能夠讓我們很輕鬆的實現在Android的各個組件以及線程之間進行傳遞消息。並且將事件的發送者與接收者之間進行解耦。而且他還是輕量級的Android類庫。對於EventBus3.0中相對於先前的版本中用法有所改變,那麼下面我們就來看一下如何使用這個EventBus;

使用方法

  對於EventBus的使用也是非常簡單的,事件的發送方將事件發出,通過EventBus將事件傳遞給改事件的訂閱者進行使用。

基本用法

  首先我們需要將EventBus添加到我們的項目中。在AndroidStudio中我們可以在gradle裏面直接配置即可。

compile 'org.greenrobot:eventbus:3.0.0'

  當然我們也可以下載EventBus的jar包導入我們的項目裏面。在這裏我們寫一個小例子來看一下EventBus最基本的用法。在這裏爲了方便我們的觀察,我們就將一個Activity分爲左右兩個部分,分別在裏面添加一個Fragment,左邊的Fragment用於事件的發送,右邊的Fragment用於接收事件。
  在這裏我們先看一下效果演示。

!這裏寫圖片描述
  我們需要創建一個實體類作爲EventBus中的事件

package com.ljd.example.eventbus;

public class MessageEvent {

    public final String message;

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

  然後我們在創建一個Activity。

package com.ljd.example.eventbus;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

  在這裏我們可以看到在Activity當中只是加載一下佈局文件,之後什麼也沒有做,其實所有的事情都交給了Fragment來處理。下面我們看一下Activity的佈局。

<?xml version="1.0" encoding="utf-8"?>
<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:orientation="horizontal"
    tools:context="com.ljd.example.eventbus.MainActivity">


    <Fragment
            android:id="@+id/left_Fragment"
            android:name="com.ljd.example.eventbus.LeftFragment"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"></Fragment>
    <TextView
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:background="@color/gray"/>
    <Fragment
            android:id="@+id/right_Fragment"
            android:name="com.ljd.example.eventbus.RightFragment"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"></Fragment>
</LinearLayout>

  在這裏我們將Fragment當做一個View,直接寫入佈局文件中。下面看一下左邊的Fragment。

package com.ljd.example.eventbus;


import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;

import org.greenrobot.eventbus.EventBus;


/**
 * A simple {@link Fragment} subclass.
 */
public class LeftFragment extends Fragment {

    private LinearLayout buttonLinear;
    public LeftFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.Fragment_left, container, false);
        buttonLinear = (LinearLayout)view.findViewById(R.id.left_Fragment_linear);
        sendEvent();
        return view;
    }

    private void sendEvent(){
        Button button = new Button(getActivity());
        button.setText("SEND");
        buttonLinear.addView(button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
            }
        });
    }
}

  這裏我們注意到我們通過EventBus.getDefault()去獲取一個EventBus對象,然後通過post方法進行發送事件。這時候就完成了事件的發佈過程。下面我們再看一下右邊的Fragment是如何接收事件的。

package com.ljd.example.eventbus;


import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;


/**
 * A simple {@link Fragment} subclass.
 */
public class RightFragment extends Fragment {

    private LinearLayout mTextViewLinear;

    public RightFragment() {
        // Required empty public constructor
    }

    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.Fragment_right, container, false);
        mTextViewLinear = (LinearLayout)view.findViewById(R.id.right_Fragment_linear);
        return view;
    }

    @Override
    public void onStop() {
        EventBus.getDefault().unregister(this);
        super.onStop();
    }

    @Subscribe
    public void onMessage(MessageEvent event) {
        TextView textView = new TextView(getActivity());
        textView.setText(event.message);
        mTextViewLinear.addView(textView);
    }
}

  對於這兩個Fragment的佈局非常簡單,裏面只包含一個垂直方向的線性佈局。在這裏就不在貼出。下面我們就來看一下EventBus是如何來接收事件的。
  如果我們需要接收某個事件並進行處理的話,首先我們需要通過EventBus.getDefault().register(this)將訂閱者的對象註冊到EventBus中,當我們不在使用這個事件時還需要通過EventBus.getDefault().unregister(this)解除註冊。這時候我們就可以再寫一個方法,參數爲所要接收事件的對象,並且在該方法上需要添加@Subscribe來標示這個方法爲接收事件的方法。這時候我們就可以接收事件並且對事件進行處理。
  在這裏有一點需要注意,在EventBus3.0中接收事件的方法是通過@Subscribe來標識的,方法名可以隨意寫。而在EventBus先前的版本中接收事件的方法必須是以onEvent開頭的四個方法。它們分別是onEvent,onEventMainThread,onEventBackground,onEventAsync。

EventBus中的線程模式

  在上面的例子中我們可以看到,我們是在主線程(也稱爲UI線程)中去提交事件,然後訂閱者也是在主線程中對事件進行的處理。但是在Android開發中我們都知道無法在主線程中去執行一個耗時的任務,並且子線程中我們也無法進行更新UI的操作。那麼這時候問題就來了,這裏再上一個例子的基礎上假設一種情形,我們在左邊的Fragment中從網絡中獲取到數據,然後將從網絡獲取到的數據在右邊的Fragment中顯示。可是在Android4.0以後我們無法在主線程中去請求網絡,也就是說我們只能在子線程中請求數據,然後在子線程中去提交事件。那麼這時候訂閱者的方法是在主線程中執行還是在子線程中執行的呢?這裏就要來看一下EventBus中的ThreadMode了。EventBus的ThreadMode總共有四種,並且都是在訂閱者中的@Subscribe裏進行制定的。下面我們就來看一下這四種ThreadMode。
  1. ThreadMode: POSTING
  這時候訂閱者執行的線程與事件的發佈者所在的線程爲同一個線程。也就是說事件由哪個線程發佈的,訂閱者就在哪個線程中執行。這個也是EventBus默認的線程模式,也就是說在上面的例子中用的就是這種ThreadMode。由於沒有線程的切換,也就意味消耗的資源也是最小的。如果一個任務不需要多線程的,也是推薦使用這種ThreadMode的。在EventBus以前的版本中對應onEvent方法。使用例子:

//與事件的提交者運行在同一個線程(默認的ThreadMode)
@Subscribe(threadMode = ThreadMode.POSTING) // 這裏的threadMode可以省略不寫
public void onMessage(MessageEvent event) {
    log(event.message);
}

  2. ThreadMode: MAIN
  從它的名字就很容易可以看出,他是在Android的主線程中運行的。如果提交的線程也是主線程,那麼他就和ThreadMode.POSTING一樣了。當然在這裏由於是在主線程中運行的,所以在這裏就不能執行一些耗時的任務。在EventBus以前的版本中對應onEventMainThread方法。使用例子:

// 在Android的主線程中運行
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
    textField.setText(event.message);
}

  3. ThreadMode: BACKGROUND
  這種模式下,我們的訂閱者將會在後臺線程中執行。如果發佈者是在主線程中進行的事件發佈,那麼訂閱者將會重新開啓一個子線程運行,若是發佈者在不是在主線程中進行的事件發佈,那麼這時候訂閱者就在發佈者所在的線程中執行任務。在EventBus以前的版本中對應onEventBackground方法。使用例子:

// 在後臺線程中執行
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
    saveToDisk(event.message);
}

  4. ThreadMode: ASYNC
  在這種模式下,訂閱者將會獨立運行在一個線程中。不管發佈者是在主線程還是在子線程中進行事件的發佈,訂閱者都是在重新開啓一個線程來執行任務。在EventBus以前的版本中對應onEventAsync方法。使用例子:

// 在獨立的線程中執行
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
    backend.send(event.message);
}

  對於EventBus的線程模式講了這麼多,下面我們來寫一個例子驗證一下上面對應的四種ThreadMode。我們就在上面的例子中添加一些內容。首先在左邊的Fragment中添加一個發送按鈕,用來提交事件。並且這個發佈者發送事件是在子線程中運行的。我們看一下關鍵代碼。

private void testThreadMode(){
    Button button = new Button(getActivity());
    button.setText("TEST THREAD MODE");
    mButtonLinear.addView(button);
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG,"訂閱者線程ID:"+Thread.currentThread().getId());
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
                }
            }).start();
        }
    });
}

  在上面發佈事件過程中我們使線程休眠5秒鐘時間,用來模擬耗時操作。下面我們在右邊的Fragment中添加如下代碼進行測試。

@Subscribe(threadMode = ThreadMode.POSTING)
public void onPostingModeMessage(MessageEvent event){

    Log.d(TAG, getResultString("ThreadMode:POSTING",event.message));
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMainModeMessage(MessageEvent event){

    Log.d(TAG, getResultString("ThreadMode:MAIN",event.message));
}

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onBackgroundModeMessage(MessageEvent event){

    Log.d(TAG, getResultString("ThreadMode:BACKGROUND",event.message));
}

@Subscribe(threadMode = ThreadMode.ASYNC)
public void onAsyncModeMessage(MessageEvent event){

    Log.d(TAG, getResultString("ThreadMode:ASYNC",event.message));
}

private String getResultString(String threadMode,String msg){
    StringBuilder sb = new StringBuilder("");
    sb.append(threadMode)
            .append("\n接收到的消息:")
            .append(msg)
            .append("\n線程id:")
            .append(Thread.currentThread().getId())
            .append("\n是否是主線程:")
            .append(Looper.getMainLooper() == Looper.myLooper())
            .append("\n");
    return sb.toString();
}

  在這裏只貼出了關鍵代碼,具體源碼可以通過下面鏈接進行下載。在這裏我們點擊這個Button發送一個事件,然後我們從logcat中看一下輸出的信息。
這裏寫圖片描述
  從上面的結果我們可以看出來由於事件的發佈者是在子線程中,所以BACKGROUND與POSTING模式下訂閱者與事件的發佈者運行在同一個線層。而ASYNC模式下又重新開起一個線程來執行任務。Main模式則是在主線程中運行。

優先級與事件的取消

  在訂閱者中我們也可以爲其設置優先級,優先級高的將會首先接收到發佈者所發佈的事件。並且我們還能在高優先中取消事件,這時候的優先級的訂閱者將接收不到事件。這類似於BroadcastReceiver中的取消廣播。不過這裏有一點我們要注意,對於訂閱者的優先級只是針對於相同的ThreadMode中。默認的優先級爲0。下面我們來做一個測試。
  在上面的例子中我們添加一下代碼。

@Subscribe(priority = 1)
public void onPriority1Message(MessageEvent event){

    Log.d(TAG, "priority = 1:" + event.message);
}

@Subscribe(priority = 2)
public void onPriority2Message(MessageEvent event){

    Log.d(TAG, "priority = 2:" + event.message);
    EventBus.getDefault().cancelEventDelivery(event) ;
}

@Subscribe(priority = 4)
public void onPriority4Message(MessageEvent event){

    Log.d(TAG, "priority = 4:" + event.message);
}

@Subscribe(priority = 3)
public void onPriority3Message(MessageEvent event){

    Log.d(TAG, "priority = 3:" + event.message);

}

  在這裏我們將優先級爲2的接收事件的方法中取消事件,這麼一來優先級爲1的將接收不到該事件。下面我們來看一下運行結果。
這裏寫圖片描述
  在這裏我們可以清楚的看到優先級高的首先接收到事件,並且成功取消該事件。

訂閱者索引

  對於上面所描述的EventBus的功能,是通過Java反射來獲取訂閱方法,這樣以來大大降低了EventBus的效率,同時也影響了我們應用程序的效率。其實對於反射的處理解析不僅僅只能夠通過Java反射的方式來進行,還能夠通過apt(Annotation Processing Tool)來處理。爲了提高效率,EventBus提供這中方式來完成EventBus的執行過程。下面就來看一下對於EventBus的另一種使用方式。
  在Project的build.gradle中添加如下代碼:

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

  然後在app的build.gradle中添加如下代碼。

apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
    arguments {
        eventBusIndex "com.example.myapp.MyEventBusIndex"
    }
}

  在當我們使用EventBus以後,在我們的項目沒有錯誤的情況下重新rebuild之後會在build目錄下面生成MyEventBusIndex文件,文件名可以自定義。下面就來看一下如何使用這個MyEventBusIndex。
  我們可以自定義設置自己的EventBus來爲其添加MyEventBusIndex對象。代碼如下所示:

EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

  我們也能夠將MyEventBusIndex對象安裝在默認的EventBus對象當中。代碼如下所示:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();

  剩下對於EventBus的用法則是一模一樣。當然也建議通過添加訂閱者索引這種方式來使用EventBus,這樣會比通過反射的方式來解析註解效率更高。

ProGuard

  當我們使用ProGuard混淆時,我們還需要在我們的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);
}

總結

  對於EventBus的使用我們就說到這裏,對於EventBus想要有更深入的瞭解,我們可以去github上下載EventBus源碼進行研究。

源碼下載

發佈了33 篇原創文章 · 獲贊 195 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章