1. 程式人生 > >最全面的EventBus 3.1的使用教程及官方推薦的結合訂閱者索引processor顯著提升效能和實際專案中的使用注意事項

最全面的EventBus 3.1的使用教程及官方推薦的結合訂閱者索引processor顯著提升效能和實際專案中的使用注意事項

需求場景

無論是現在的專案還是以前的專案中,都會遇見執行緒之間通訊,元件之間通訊的需求,我們知道這些需求都可以使用EventBus來處理,為了對比體現出EventBus使用的方便簡潔,我們先來回顧下在EventBus出現以前我們是怎麼處理執行緒間通訊和元件間通訊的。
1,執行緒間通訊 + Handler訊息傳遞機制
一個Android應用程式被建立的時候都會建立一個UI主執行緒,但是有時候我們會有一些比較耗時的操作,比如請求網路,為了防止阻塞UI執行緒,導成應用程式ANR,我們會將耗時的操作放到子執行緒中進行處理,而處理完之後又需要操作UI,但是Android不允許子執行緒操作UI並且規定更新UI的操作必須在UI執行緒中執行,此時就涉及到了子執行緒和UI執行緒之間的通訊,在EventBus出現之前我們的處理的方式都是:Handler訊息傳遞機制,具體的參見

http://www.cnblogs.com/whoislcj/p/5590615.html
2,元件之間通訊 + Intent / 廣播
點贊資料的同步:在詳情頁對該作品進行點贊,需要同時更新列表頁展示時該作品的點贊數量,EventBus出來以前我們可以使用廣播來完成,具體參見 http://www.cnblogs.com/whoislcj/p/5593056.html
3,代替Intent傳遞複雜資料
另外阿里安卓開發文件中明確指定:對於資料量比較大的Activity之間的資料通訊,建議避免使用Intent+Parcelable(序列化介面)的方式,可以考慮使用EventBus代替,以免造成TransactionTooLargeException錯誤使應用崩潰。
我們知道如果想要在兩個activity之間傳遞物件,那麼這個物件必須序列化,android中序列化一個物件有兩種方式,一種是實現Serializable介面,這個非常簡單,只需要宣告一下就可以了。但是android中還有一種特有的序列化方法,那就是實現Parcelable介面,使用這種方式來序列化的效率要高於實現Serializable介面,但是這種序列化方式實現起來非常麻煩,此處不詳細講解,讀者自行去百度。

此外當利用Intent向Activity傳遞資料的時候,傳遞資料時有一個緩衝區,而這個緩衝區最大隻有1MB,所以getIntent().getSerializableExtra()等方法攜帶的資料不宜過大,否則容易報錯TransactionTooLargeException導致程式崩潰。
鑑於以上的原因使用EventBus來代替Intent傳遞複雜資料就顯得尤為有必要了,至於怎麼樣傳遞,見本文。

EventBus簡介

EventBus由greenrobot組織貢獻(該組織還貢獻了greenDAO),是一種用於Android的事件釋出(publish)—訂閱(subscribe)匯流排,主要用來代替Intent,Handler,Broadcast在Fragment,Activity,Service,執行緒之間傳遞訊息,簡化各元件之間的通訊,優缺點如下:

優點:簡化元件之間的通訊方式,實現解耦讓業務程式碼更加簡潔,可以動態設定事件處理執行緒以及優先順序,EventBus 3.0中添加了新的效能訂閱者索引來顯著提升效率,具體使用見文末。
缺點:每個事件都必須自定義一個事件類,造成事件類太多,無形中加大了維護成本。
GitHub地址:https://github.com/greenrobot/EventBus

EventBus 3.0使用

1,新增依賴

compile 'org.greenrobot:eventbus:3.1.1'

2,定義一個事件型別

public class DataSynEvent {
    private int count;

    public int getCount() {
        return count;
    }

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

其實這個類就是一個Bean類,裡面定義用來傳輸的資料的型別。

3,註冊/解除註冊
當我們需要在Activity或者Fragment裡訂閱事件時,我們需要註冊EventBus。我們一般選擇在Activity的onCreate()方法裡去註冊EventBus,在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();
    if(EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this);  // 解除註冊
        }
}

4,釋出事件

EventBus.getDefault().post(new DataSynEvent());

5,訂閱事件處理

@Subscribe(threadMode = ThreadMode.MAIN) //在ui執行緒執行
// 3.0之後事件處理的方法名可以隨意取,不過需要加上註解@subscribe(),並且指定執行緒模型,預設是POSTING。
public void onDataSynEvent(DataSynEvent event) {
     Log.e(TAG, "event---->" + event.getCount());
}

至此,經過以上5個步驟我們就可以簡單的使用EventBus了。
EventBus的註冊和反註冊以及事件處理的方法我們一般單獨寫在一個activity中,而事件的傳送我們一般會寫在另外一個activity中,而負責事件傳送的activity中我們不必去寫註冊與反註冊的邏輯程式碼。

EventBus的執行緒模式

EventBus支援訂閱者方法在不同於釋出事件所線上程的執行緒中被呼叫。你可以使用執行緒模式來指定呼叫訂閱者方法的執行緒。EventBus總共支援5種執行緒模式:
1,ThreadMode.POSTING 訂閱者方法將在釋出事件所在的執行緒中被呼叫。這是 預設的執行緒模式。事件的傳遞是同步的,一旦釋出事件,所有該模式的訂閱者方法都將被呼叫。這種執行緒模式意味著最少的效能開銷,因為它避免了執行緒的切換。因此,對於不要求是主執行緒並且耗時很短的簡單任務推薦使用該模式。使用該模式的訂閱者方法應該快速返回,以避免阻塞釋出事件的執行緒,這可能是主執行緒。
2,ThreadMode.MAIN 訂閱者方法將在主執行緒(UI執行緒)中被呼叫。因此,可以在該模式的訂閱者方法中直接更新UI介面。如果釋出事件的執行緒是主執行緒,那麼該模式的訂閱者方法將被直接呼叫。使用該模式的訂閱者方法必須快速返回,以避免阻塞主執行緒。
3,ThreadMode.MAIN_ORDERED 訂閱者方法將在主執行緒(UI執行緒)中被呼叫。因此,可以在該模式的訂閱者方法中直接更新UI介面。事件將先進入佇列然後才傳送給訂閱者,所以釋出事件的呼叫將立即返回。這使得事件的處理保持嚴格的序列順序。使用該模式的訂閱者方法必須快速返回,以避免阻塞主執行緒。
4,ThreadMode.BACKGROUND 訂閱者方法將在後臺執行緒中被呼叫。如果釋出事件的執行緒不是主執行緒,那麼訂閱者方法將直接在該執行緒中被呼叫。如果釋出事件的執行緒是主執行緒,那麼將使用一個單獨的後臺執行緒,該執行緒將按順序傳送所有的事件。使用該模式的訂閱者方法應該快速返回,以避免阻塞後臺執行緒。
5,ThreadMode.ASYNC 訂閱者方法將在一個單獨的執行緒中被呼叫。因此,釋出事件的呼叫將立即返回。如果訂閱者方法的執行需要一些時間,例如網路訪問,那麼就應該使用該模式。避免觸發大量的長時間執行的訂閱者方法,以限制併發執行緒的數量。EventBus使用了一個執行緒池來有效地重用已經完成呼叫訂閱者方法的執行緒。

黏性事件

普通的事件我們通過post傳送給EventBus,傳送過後之後當前已經訂閱過的方法可以收到。但是如果有些事件需要所有訂閱了該事件的方法都能執行呢?例如一個Activity,要求它管理的所有Fragment都能執行某一個事件,但是當前我只初始化了3個Fragment,如果這時候通過post傳送了事件,那麼當前的3個Fragment當然能收到。但是這個時候又初始化了2個Fragment,那麼我必須重新發送事件,這兩個Fragment才能執行到訂閱方法。
粘性事件就是為了解決這個問題,通過 postSticky 傳送粘性事件,這個事件不會只被消費一次就消失,而是一直存在系統中,知道被 removeStickyEvent 刪除掉。那麼只要訂閱了該粘性事件的所有方法,只要被register 的時候,就會被檢測到,並且執行。訂閱的方法需要新增 sticky = true 屬性。

訂閱和釋出一個粘性事件的示例程式碼如下所示:

// 訂閱粘性事件
@Subscribe(sticky = true)
public void onMessageEvent(MessageEvent event) {
    ...
}

// 釋出粘性事件
EventBus.getDefault().postSticky(new MessageEvent("Hello EventBus!"));

釋出一個粘性事件之後,EventBus將一直快取該粘性事件。如果想要移除粘性事件,那麼可以使用如下方法:

// 移除指定的粘性事件
removeStickyEvent(Object event);

// 移除指定型別的粘性事件
removeStickyEvent(Class<T> eventType);

// 移除所有的粘性事件
removeAllStickyEvents();

如下:

 @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onMessageEvent(MessageEvent event) {
        Log.i(TAG, "message is " + event.getMessage());
        // 更新介面
        mTvMessage.setText(event.getMessage());
        // 移除粘性事件
        EventBus.getDefault().removeStickyEvent(event);
    }

上面當接收到MessageEvent粘性事件時,訂閱者方法將列印日誌訊息,並更新介面上的TextView,最後移除該粘性事件。

設定訂閱事件的優先順序和取消事件傳遞

EventBus支援在定義訂閱者方法時指定事件傳遞的優先順序。預設情況下,訂閱者方法的事件傳遞優先順序為0。數值越大,優先順序越高。在相同的執行緒模式下,更高優先順序的訂閱者方法將優先接收到事件。注意:優先順序只有在相同的執行緒模式下才有效。

@Subscribe(threadMode = ThreadMode.MAIN,priority = 100) //在ui執行緒執行 優先順序100
    public void onDataSynEvent(DataSynEvent event) {
        Log.e(TAG, "event---->" + event.getCount());
    }

你可以在高優先順序的訂閱者方法接收到事件之後取消事件的傳遞。此時,低優先順序的訂閱者方法將不會接收到該事件。
注意: 訂閱者方法只有在執行緒模式為ThreadMode.POSTING時,才可以取消一個事件的傳遞。
取消事件傳遞的示例程式碼如下所示:

 @Subscribe(threadMode = ThreadMode.POSTING, priority = 1)
public void onMessageEvent(MessageEvent event) {
    ...
    // 取消事件傳遞
    EventBus.getDefault().cancelEventDelivery(event);
}

EventBus是如何代替Handler完成執行緒間訊息傳遞的?

在子執行緒傳送的訊息,在接受的地方切換到主執行緒,只需要增加一個引數即可,比Handler使用方便太多了,具體如下:

new Thread(new Runnable() {
                    @Override
                    public void run() {
                        EventBus.getDefault().post(666);
                    }
                }).start();
MainActivity裡面接收事件,只需要指定執行緒模式即可,即threadMode = ThreadMode.MAIN
@Subscribe(threadMode = ThreadMode.MAIN)
public void getEventBus(Integer num) {
    if (num != null) {
         Toast.makeText(this, "num" + num, Toast.LENGTH_SHORT).show();
    }
}

EventBus是如何代替Intent傳遞複雜資料的?

文章最開始我們提到,阿里巴巴安卓開發文件中明確表示對於資料量比較大的Activity之間的資料通訊,建議避免使用Intent+Parcelable(序列化介面)的方式,可以考慮使用EventBus代替,以免造成TransactionTooLargeException錯誤使應用崩潰。具體原因在上面我們也分析過了。
使用EventBus代替Intent傳遞複雜資料目前有兩種方式(推薦使用方式2也是阿里安卓開發推薦的方式):
1,呼叫startActivity啟動介面時先不用呼叫EventBus的post去傳參。而是在當前介面的onStop中呼叫post去傳遞你想傳的複雜引數。為什麼是onStop裡面呢?因為這個方法是在下一個介面成功啟動後呼叫的。這樣你就繞過序列化了,如下:

@Override
    protected void onStop() {
        super.onStop();
        EventBus.getDefault().post(new ReplyDetailEvent());
    }

但這樣可能會存在記憶體洩漏,那樣可能要對導致記憶體洩漏的物件再做處理,就又變的麻煩了,所以不推薦使用該方式。
2,通過EventBus的黏性事件處理(阿里安卓開發推薦)
傳遞資料:

/**
     * @desc 點擊發送資料過去第二個介面
     * @author lzPeng
     * @time 2018/4/27  20:15
     */
    public void btnSend(View view) {
        EventBus.getDefault().postSticky(new ObjEvent(etName.getText().toString(), etAge.getText().toString()));
        startActivity(new Intent(OneActivity.this, TwoActivity.class));
    }

接收資料時:


//用來賦值傳遞過來的物件
ObjEvent mObjEvent;

/**
     * @desc 接收第一個介面傳遞過來的資料
     * @author lzPeng
     * @time 2018/4/27  20:15
     */
   @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_two);
    //註冊繫結事件即可接收相關資料
    EventBus.getDefault().register(this);
    //處理相關資料
    if (null != mObjEvent) {
        ((TextView) findViewById(R.id.tv_data)).setText("name:" + mObjEvent.getName() + "\n\n" + "age:" + mObjEvent.getAge());
    }
}


    @Subscribe(threadMode = ThreadMode.POSTING, sticky = true)
    public void onDataEvent(ObjEvent objEvent) {
        if (null != objEvent) {
            //賦值
            this.mObjEvent = objEvent;
        }
    }

@Override
protected void onDestroy() {
    //移除全部粘性事件
    EventBus.getDefault().removeAllStickyEvents();
    //解綁事件
    EventBus.getDefault().unregister(this);
    super.onDestroy();
}

使用該方式在定義bean物件ObjEvent時不用進行序列化仍然可以進行傳遞。

EventBus 3.0 官方推薦新特性:結合訂閱者索引processor使用,顯著提升效能

EventBus提供了一個EventBusAnnotationProcessor註解處理器來在編譯期通過讀取@Subscribe()註解並解析,處理其中所包含的資訊,然後生成java類來儲存所有訂閱者關於訂閱的資訊,這樣就比在執行時使用反射來獲得這些訂閱者的資訊速度要快。

預設情況下,EventBus在查詢訂閱者方法時採用的是反射。訂閱者索引是EventBus 3的一個新特性。它可以加速訂閱者的註冊,是一個可選的優化。訂閱者索引的原理是:使用EventBus的註解處理器在應用構建期間建立訂閱者索引類,該類包含了訂閱者和訂閱者方法的相關資訊。EventBus官方推薦在Android中使用訂閱者索引以獲得最佳的效能。

要開啟訂閱者索引的生成,你需要在構建指令碼中使用annotationProcessor屬性將EventBus的註解處理器新增到應用的構建中,還要設定一個eventBusIndex引數來指定要生成的訂閱者索引的完全限定類名。

1,首先,修改模組下的build.gradle構建指令碼:

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [eventBusIndex: 'com.github.cyc.eventbus.subscriberindexdemo.MyEventBusIndex']
            }
        }
    }
    ...
}

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

2,然後build一下工程,EventBus註解處理器將自動為你生成一個訂閱者索引類。我們在\build\generated\source\apt\PakageName\下看到通過註解分析生成的索引類,這樣我們便可以在初始化EventBus時應用我們生成的索引了,生成的索引類程式碼如下:

package com.github.cyc.eventbus.subscriberindexdemo;

import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberMethodInfo;
import org.greenrobot.eventbus.meta.SubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

import org.greenrobot.eventbus.ThreadMode;

import java.util.HashMap;
import java.util.Map;

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onMessageEvent", MessageEvent.class, ThreadMode.MAIN),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

3,最後在應用自定義的Application類的onCreate()方法中將訂閱者索引類新增到EventBus預設的單例中,示例程式碼如下所示:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 配置EventBus
        EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
    }
}

經過以上操作我們再通過之前的方式EventBus.getDefault()來獲取EventBus物件的時候,雖然兩種情況下獲取物件的方式一樣,但此時我們獲得的物件本身已經具備了訂閱者索引新特性了,相當於優化加強版。
之後再按照上面講的正常使用EventBus,就會發現效率有顯著提升。
例如新增前後註冊效率對比:
兩種情況下分別進行註冊:EventBus.getDefault().register(this);
新增之前:註冊過程使用了9毫秒
在這裡插入圖片描述
新增之後:註冊過程只用了2毫秒
在這裡插入圖片描述

實際專案中使用提示

1,註冊一般寫在onCreate或者onStart中,儘量不要寫在onResume,可能出現多次註冊。
2,取消註冊一定寫在onDestory,寫在onStop可能會引發異常。
3,一般建議在BaseActivity中進行註冊和取消註冊,此外如果actiivty註冊了eventbus,而沒寫一個方法被@Subscribe註解就會報異常,所以一般在BaseActivity中新增一個帶@Subscribe註解的方法預設接收事件,可能該事件永遠也不會觸發,因為我們可能永遠不會發送該引數指定的BaseActivity型別的事件,所以該預設的事件處理方法存在的目的只是為了解決前面所說的異常而存在。

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onStart() {
        super.onStart();
        // 繫結
        EventBus.getDefault().register(this);
    }

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

    /**
     * 預設繫結一個事件,防止原始碼裡面去找方法的時候找不到報錯。
     * @param activity
     */
    @Subscribe
    public void onEvent(BaseActivity activity){
        
    }
}

EventBus外掛的使用

專案使用EventBus ,裡面各種事件亂竄,這個外掛對於專案中事件橫飛的情況,還是非常好用的。
在AndroidStudio Setting——–>Plugins:
在這裡插入圖片描述
點選post事件小圖示,會直接列出所有接收的地方:
在這裡插入圖片描述
點選接收事件的圖示,會直接列出所有傳送事件的地方:
在這裡插入圖片描述

程式碼混淆

-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);
}