EventBus,輕松實現跨組件跨線程通信

安卓基礎開發庫,讓開發簡單點。
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.lxweimin.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之后,其性能提高了許多倍。

性能對比

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

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成功,如下圖所示。

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結束

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

推薦閱讀更多精彩內容