EventBus,輕鬆實現跨元件跨執行緒通訊

JYcoder發表於2018-03-31

安卓基礎開發庫,讓開發簡單點。
DevRing & Demo地址https://github.com/LJYcoder/DevRing

學習/參考地址:
http://blog.csdn.net/itachi85/article/details/52205464
http://blog.csdn.net/Tencent_Bugly/article/details/51354693
http://blog.csdn.net/qq_28746251/article/details/51476389

前言

EventBus是一個基於釋出/訂閱的事件匯流排(資料通訊框架),它簡化了元件之間、執行緒之間的資料通訊操作,並且耦合度低、開銷小。
3.0版本後,使用註解來宣告訂閱者函式及其相關屬性,使得操作流程更加便捷,還提供index幫助提升其效能。

(如果你不喜歡用EventBus,而想用RxJava自己封裝一個RxBus來實現通訊,
可以參考http://www.jianshu.com/p/3a3462535b4d


介紹

下面從 配置、基本使用、粘性事件、使用index優化、簡單封裝、混淆 這幾個部分來介紹EventBus。

1. 配置

在Module下的build.gradle中新增

//EventBus
compile 'org.greenrobot:eventbus:3.0.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'//用於eventbus開啟Index加速

2. 基本使用

使用步驟分為定義事件、訂閱事件、傳送事件、處理事件、取消訂閱五步

2.1 定義事件

先定義一個你打算髮送的事件類,裡面新增你要傳送的資料變數。
變數的型別除了基本資料型別,也可以是自定義的實體類。

public class MovieEvent {
    private int count;

    public MovieEvent(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

2.2 訂閱事件

在你要接收事件的地方訂閱事件:

public class MovieActivity{
    ....

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //確保之前未訂閱過,再呼叫訂閱語句,以免報錯
        if(!EventBus.getDefault().isRegistered(subscriber)){
            EventBus.getDefault().register(subscriber);
        }
    }

    ....
}

2.3 傳送事件

在你要傳送事件的地方,呼叫

EventBus.getDefault().post(new MovieEvent(1));

這裡需要注意,傳送的事件是屬於引用傳遞,也就是說,傳送事件後,你在事件處理函式中對接收到的事件進行了修改,那麼傳送源頭的事件也會跟著改變。所以如果不想影響到傳送源頭的資料,建議new物件後再傳送。

另外,EventBus提供了一個方法用於傳送粘性事件,粘性事件相關內容會在下面另外介紹。

EventBus.getDefault().postSticky(new MovieEvent(1));

2.4 處理事件

在接收事件的地方新增處理事件的方法,請與訂閱事件方法處於同一個類下,以保證能成功訂閱事件

public class MovieActivity{
    ....

    //宣告處理事件的方法
    @Subscribe
    public void handlerEvent(MovieEvent event) {
        //處理事件
        int count = event.getCount();
        ...
    }

     ....
}

你可以自定義處理事件方法的名稱,但必須加上@Subscribe註解來宣告該方法為事件接收處理方法。
方法的引數用於指定你要接收事件型別。比如引數為MovieEvent event,則表示接收型別為MovieEvent的事件,其他型別的事件將不會接收到。

另外@Subscribe裡面可以對 處理事件時所在的執行緒、事件接收的優先順序、是否為粘性事件 進行設定

  • 處理事件時所在的執行緒
@Subscribe(threadMode = 執行緒型別)

執行緒型別有以下四種選擇:

  1. ThreadMode.POSTING:預設的型別。表示處理事件時所在的執行緒將會與事件傳送所在的執行緒一致,也就是兩者的執行都處於同一個執行緒。
  2. ThreadMode.MAIN:表示處理事件時所在的執行緒將切換為UI主執行緒。如果傳送事件所在的執行緒本來就是UI主執行緒,則不會切換。
  3. ThreadMode.BACKGROUND:表示處理事件時所在的執行緒將切換為後臺執行緒。如果傳送事件所在的執行緒本來就是後臺執行緒,則不會切換。
  4. ThreadMode.ASYNC:表示處理事件時所在的執行緒將會切換為一個新建的獨立子執行緒。
  • 優先順序
@Subscribe(priority = 100)

priority用於指定接收事件的優先順序,預設值為0。
優先順序高的事件處理函式將先收到傳送的事件,你可以在優先順序高的事件處理函式中攔截事件,不讓它繼續往下傳遞,攔截方法如下

EventBus.getDefault().cancelEventDelivery(event);
  • 粘性事件
@Subscribe(sticky = true)

sticky用來宣告是否接收訂閱前就已發出的粘性事件,預設值為false,具體介紹請看後面的“粘性事件”

2.5 取消訂閱

在退出或者不需要接收事件時,取消訂閱

public class MovieActivity{
    ....

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消訂閱
        if (EventBus.getDefault().isRegistered(subscriber)) {
            EventBus.getDefault().unregister(subscriber);
        }
    }

    ....
}

3. 粘性事件

一般我們的使用流程為:訂閱事件---》傳送事件---》接收處理事件。
那如果現在希望傳送事件---》訂閱事件---》接收處理事件,這可以實現嗎?答案是可以的。
EventBus提供的粘性事件便可實現這一場景。

3.1 使用步驟

使用步驟和普通事件的基本一樣,但有兩點需注意:

  • 註冊事件接收的操作(EventBus.getDefault().register(this);)需在控制元件初始化後再執行,否則會接收不到,這裡建議放在onStart的生命週期中執行。
  • 事件的傳送呼叫的是postSticky(event),事件處理函式需宣告@Subscriber(sticky = true)。
//事件傳送方

//傳送粘性事件
EventBus.getDefault().postSticky(new MovieEvent(1));
//事件接收處理方

//傳送完粘性事件後再進行訂閱事件
EventBus.getDefault().register(this);//註冊事件接收

//接收處理訂閱前發出的粘性事件
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void handleEvent(MovieEvent event) {
    //處理事件
    int count = event.getCount();
}

3.2 使用場景

這裡舉一個使用場景:Activity間跳轉傳值。參考自http://www.cnblogs.com/ldq2016/p/5387444.html
我們平時都是使用Intent攜帶資料來實現,如果要傳遞的是自定義的實體類,還需要進行序列化操作。下面大致演示如何使用EventBus的粘性事件來實現這一場景。

ActivityA跳轉到ActivityB,並將Movie物件傳遞過去。

//ActivityA中的程式碼

Movie movie = new Movie();
//傳送粘性事件,傳送movie
EventBus.getDefault().postSticky(movie);
//跳轉到ActivityB
startActivity(new Intent(this, ActivityB.class));
//ActivityB中的程式碼

//訂閱事件
EventBus.getDefault().register(this);

//獲取訂閱前ActivityA傳送的粘性事件
@Subscribe(sticky = true)
public void getDataFromOtherActivity(Movie movie) {
    //得到ActivityA的Movie物件,進行具體操作。
}

如果大家還有其他使用場景,歡迎留言分享~

3.3 補充

1)EventBus僅儲存最新傳送的粘性事件。
2)手動獲取、移除粘性事件

//手動獲取粘性事件
MovieEvent movieEvent = EventBus.getDefault().getStickyEvent(MovieEvent.class);

if(movieEvent != null) {
    //移除粘性事件
    EventBus.getDefault().removeStickyEvent(movieEvent);
}

3)
傳送粘性事件後,對於在傳送前就已經訂閱事件的訂閱者,它們都會收到型別相符的粘性事件,不管它們的事件處理方法是否宣告為sticky=true。
而對於在傳送後才進行訂閱事件的訂閱者,其事件處理方法必須宣告為sticky=true才能收到型別相符的粘性事件。

4. 使用index優化

在3.0版本之前,為了保證效能,EventBus在遍歷尋找訂閱者的回撥方法時使用反射而不是註解,而在3.0版本由於採用註解,從下圖可以看到,其效能比2.4版本要下降很多。
為了在使用註解的情況下保證高效能,EventBus提供了通過開啟Index來提升效能的方法,從下圖可以看到,開啟了Index之後,其效能提高了許多倍。

8706116-4db4e5f69cef1b50
效能對比

下面介紹如何生成和開啟索引。

4.1 生成索引

網上很多關於Index的文章中,是通過新增以下配置來生成的

//project下的build.gradle檔案

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

//module下的build.gradle檔案

apply plugin: 'com.neenbedankt.android-apt'
apt {
    arguments {
        eventBusIndex "com.dev.base.MyEventBusIndex"
    }
}
dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

但是,如果你的專案中也使用了ButterKnife庫,那麼新增上面的配置後會導致ButterKnife無法正常工作;另外,android-apt的作者已在官網發表宣告表示後續將不會繼續維護android-apt,所以並不推薦這個方式實現,而是使用Android推出的官方外掛annotationProcessor來替代apt。
通過annotationProcessor來設定生成index的配置會更加便捷,如下:

//module下的build.gradle檔案

android{
    defaultConfig {
            //...省略其他配置

            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [ eventBusIndex : "com.dev.base.MyEventBusIndex" ]
               }
            }
    }
}


dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

新增以上配置後,編譯 執行 專案,執行了一次“傳送事件”操作後,如果在\build\generated\source\apt\debug\專案包名\下生成了你指定的Index類,則表示生成index成功,如下圖所示。

8706116-208a1f75c406162c
index生成後的位置

4.2 開啟索引

生成index之後,在Appliction中呼叫addIndex()方法開啟即可。

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

5. 簡單封裝

public class EventBusManager {

    //開啟Index加速
    public static void openIndex() {
        EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
    }

    //訂閱事件
    public static void register(Object subscriber) {
        if(!EventBus.getDefault().isRegistered(subscriber)){
            EventBus.getDefault().register(subscriber);
        }
    }

    //取消訂閱
    public static void unregister(Object subscriber) {
        if (EventBus.getDefault().isRegistered(subscriber)) {
            EventBus.getDefault().unregister(subscriber);
        }
    }

    //終止事件繼續傳遞
    public static void cancelDelivery(Object event) {
        EventBus.getDefault().cancelEventDelivery(event);
    }

    //獲取儲存起來的粘性事件
    public static <T> T getStickyEvent(Class<T> classType){
        return EventBus.getDefault().getStickyEvent(classType);
    }

    //刪除儲存中的粘性事件
    public static void removeStickyEvent(Object event) {
        EventBus.getDefault().removeStickyEvent(event);
    }

    //傳送事件
    public static void postEvent(Object event){
        EventBus.getDefault().post(event);
    }

    //傳送粘性事件
    public static void postStickyEvent(Object event) {
        EventBus.getDefault().postSticky(event);
    }

}

DevRing/Demo中已對EventBus進行了封裝,在Activity和Fragment中,只需重寫isUseEventBus()方法並返回true,則會自動對該頁面進行訂閱和解除訂閱的操作,詳情請查閱程式碼。

6. 混淆

在proguard-rules.pro檔案中新增以下內容進行混淆配置

#EventBus開始
-keepattributes *Annotation*
#如果使用了EventBus index進行優化加速,就必須加上這個
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}

-keep enum org.greenrobot.eventbus.ThreadMode { *; }
#如果使用了Async型別的執行緒
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}
#EventBus結束

相關文章