Android訊息傳遞之EventBus 3.0使用詳解

總李寫程式碼發表於2016-06-20

前言:

    前面兩篇不僅學習了子執行緒與UI主執行緒之間的通訊方式,也學習瞭如何實現元件之間通訊,基於前面的知識我們今天來分析一下EventBus是如何管理事件匯流排的,EventBus到底是不是最佳方案?學習本篇知識之前建議先回顧一下前兩篇知識:Android訊息傳遞之Handler訊息機制(一)Android訊息傳遞之元件間傳遞訊息(二)

  訊息傳遞相關文章地址:

EventBus產生需求背景:

    在做專案的時候往往需要應用程式內各元件間、元件與後臺執行緒間的通訊。比如耗時操作,等耗時操作完成後通過Handler或Broadcast將結果通知給UI,N個Activity之間需要通過Listener通訊,之前的實現方式我們在Android訊息傳遞之元件間傳遞訊息(二)中已經介紹過了,其實這些都可以通過EventBus輕鬆實現,EventBus通過釋出/訂閱(publish/subscribe)方式來管理事件匯流排。其實EventBus的實現方式更加接近上篇文章的方式二,不同的是EventBus通過註解和反射機制 將訂閱者連同訂閱函式儲存起來,然後在傳送訂閱的時候 遍歷訂閱函式陣列進行呼叫,其實從這方面就可以EventBus執行效率多少會受到一點影響。

EventBus介紹:

     EventBus出自greenrobot,和之前大名鼎鼎的GreenDao出自同一家。之前一直使用的是2.4版本,今天我們將學習分析最新的Event 3.0,EventBus 3.0 最新的特性就是加入了註解,通過註解的方式 告知訂閱函式執行在哪個執行緒中。

     github地址:https://github.com/greenrobot/EventBus

     官方文件:http://greenrobot.org/eventbus/documentation

EventBus主要角色:

  •  Event 傳遞的事件物件
  •  Subscriber  事件的訂閱者 
  •  Publisher  事件的釋出者
  •  ThreadMode 定義函式在何種執行緒中執行

  官網給出的各種角色的協作圖

EventBus配置:

  EventBus框架也是採用建造者模式設計的,可以通過EventBusBuilder來設定一些配置資訊,例如設定debug模式下要丟擲異常

EventBus eventBus=EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).build();

EventBus示例:

 之前做圖片社交App的時候,需要處理一個點贊資料的同步,比如在作品的詳情頁點贊 需要同時更新列表頁該作品的點贊數量,這裡還是以此為例。

 1.)build.gradle新增引用 
compile 'org.greenrobot:eventbus:3.0.0'
2.)定義一個事件型別
public class DataSynEvent {
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}
3.)訂閱/解除訂閱

 訂閱

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

 解除訂閱

EventBus.getDefault().unregister(this);//解除訂閱
4.)釋出事件
EventBus.getDefault().post(new DataSynEvent());
5.)訂閱事件處理
    @Subscribe(threadMode = ThreadMode.MAIN) //在ui執行緒執行
    public void onDataSynEvent(DataSynEvent event) {
        Log.e(TAG, "event---->" + event.getCount());
    }
ThreadMode總共四個:
  • NAIN UI主執行緒
  • BACKGROUND 後臺執行緒
  • POSTING 和釋出者處在同一個執行緒
  • ASYNC 非同步執行緒
6.)訂閱事件的優先順序

   事件的優先順序類似廣播的優先順序,優先順序越高優先獲得訊息

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

 傳送有序廣播可以終止廣播的繼續往下傳遞,EventBus也實現了此功能

  EventBus.getDefault().cancelEventDelivery(event) ;//優先順序高的訂閱者可以終止事件往下傳遞
8.)處理程式碼混淆
-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除了普通事件也支援粘性事件,這個有點類似廣播分類中的粘性廣播。本身粘性廣播用的就比較少,為了方便理解成訂閱在釋出事件之後,但同樣可以收到事件。訂閱/解除訂閱和普通事件一樣,但是處理訂閱函式有所不同,需要註解中新增sticky = true

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

傳送粘性事件

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

對於粘性廣播我們都比較清楚屬於常駐廣播,對於EventBus粘性事件也類似,我們如果不再需要該粘性事件我們可以移除

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

或者呼叫移除所有粘性事件

  EventBus.getDefault().removeAllStickyEvents();

EventBus processor使用:

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

 1.)具體使用:在build.gradle中新增如下配置
buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
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.whoislcj.eventbus.MyEventBusIndex"
    }
}
2.)使用索引

此時編譯一次,自動生成生成索引類。在\build\generated\source\apt\PakageName\下看到通過註解分析生成的索引類,這樣我們便可以在初始化EventBus時應用我們生成的索引了。

自動生成的程式碼

/** 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(com.whoislcj.testhttp.MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onDataSynEvent", com.whoislcj.testhttp.eventBus.DataSynEvent.class,
                    ThreadMode.MAIN, 100, false),
            new SubscriberMethodInfo("onDataSynEvent1", com.whoislcj.testhttp.eventBus.TestEvent.class, ThreadMode.MAIN,
                    0, true),
        }));

    }

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

新增索引到EventBus預設的單例中

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
3.)對比新增前後註冊效率對比
分別EventBus.getDefault().register(this);

  新增之前:前後用了9毫秒

  

  新增之後:前後用了2毫秒 

 EventBus優缺點:

   優點:簡化元件之間的通訊方式,實現解耦讓業務程式碼更加簡潔,可以動態設定事件處理執行緒以及優先順序

   缺點:目前發現唯一的缺點就是類似之前策略模式一樣的詬病,每個事件都必須自定義一個事件類,造成事件類太多,無形中加大了維護成本

EventBus 3.0 與2.x的區別

 1.)程式碼更加簡潔

   EventBus 2.x 必須定義以onEvent開頭的幾個方法,程式碼中語境比較突兀,且有可能會導致拼寫錯誤,例如資料同步事件

    public void onEvent(DataSynEvent event) {
        //事件在哪個執行緒釋出出來的,onEvent就會在這個執行緒中執行, 同 @Subscribe(threadMode = ThreadMode.POSTING)
    }

    public void onEventMainThread(DataSynEvent event) {
        // 不論事件是在哪個執行緒中釋出出來的,onEventMainThread都會在UI執行緒中執行,接收事件就會在UI執行緒中執行,同 @Subscribe(threadMode = ThreadMode.MAIN)
    }

    public void onEventBackgroundThread(DataSynEvent event) {
        //那麼如果事件是在UI執行緒中釋出出來的,那麼onEventBackground就會在子執行緒中執行,如果事件本來就是子執行緒中釋出出來的,那麼onEventBackground函式直接在該子執行緒中執行,同 @Subscribe(threadMode = ThreadMode.BACKGROUND)
    }


    public void onEventAsync(DataSynEvent event) {
        //使用這個函式作為訂閱函式,那麼無論事件在哪個執行緒釋出,都會建立新的子執行緒在執行onEventAsync,同 @Subscribe(threadMode = ThreadMode.ASYNC)
    }

EventBus  3.0 函式名字不再受到許可權,而且可以在一個函式中體現出在哪個執行緒執行,並且可指定接收事件的優先順序

 /**
     * 普通事件
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN, priority = 100) 
    public void onDataSynEvent(DataSynEvent event) {
       
    }

    /**
     * 粘性事件
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN, priority = 100, sticky = true) 
    public void onDataSynEvent(DataSynEvent event) {
       
    }

EventBus 2.x 註冊方式也比較繁瑣

  public void register(Object subscriber) {
  register(subscriber, false, 0);
  }
  
  public void register(Object subscriber, int priority) {
  register(subscriber, false, priority);
  }
  
  public void registerSticky(Object subscriber) {
  register(subscriber, true, 0);
  }
  
  public void registerSticky(Object subscriber, int priority) {
  register(subscriber, true, priority);
  }
  private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
  ...
  }

EventBus  3.0 註冊方式只有一個

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

以上還是在一個訂閱者僅僅訂閱一個事件的情況下,如果訂閱多個事件,可想而知EventBus 2.x勢必導致訂閱者要寫大量的多型函式,如果訂閱多種型別事件,比如普通事件和粘性事件並存,估計要同時呼叫register,registerSticky兩個函式。

2.)效能更優

  EventBus 2.x 是採用反射的方式對整個註冊的類的所有方法進行掃描來完成註冊,當然會有效能上的影響。EventBus  3.0中EventBus提供了EventBusAnnotationProcessor註解處理器來在編譯期通過讀取@Subscribe()註解並解析、處理其中所包含的資訊,然後生成java類來儲存所有訂閱者關於訂閱的資訊,這樣就比在執行時使用反射來獲得這些訂閱者的資訊速度要快

小結:

     EventBus 3.0的使用基本上總結完了,之前一直擔心EventBus通過註解或者反射會影響太多效能,隨著3.0的釋出這部分影響已經很小了。

 知識擴充套件:

     有關EventBus實現原理已經有大神做了非常細緻的解說,這裡就不做具體分析了,參考部落格地址:http://www.jianshu.com/p/f057c460c77e

相關文章