前言:
前面兩篇不僅學習了子執行緒與UI主執行緒之間的通訊方式,也學習瞭如何實現元件之間通訊,基於前面的知識我們今天來分析一下EventBus是如何管理事件匯流排的,EventBus到底是不是最佳方案?學習本篇知識之前建議先回顧一下前兩篇知識:Android訊息傳遞之Handler訊息機制(一),Android訊息傳遞之元件間傳遞訊息(二)。
訊息傳遞相關文章地址:
- Android訊息傳遞之Handler訊息機制
- Android訊息傳遞之元件間傳遞訊息
- Android訊息傳遞之EventBus 3.0使用詳解
- Android訊息傳遞之基於RxJava實現一個EventBus - RxBus
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的釋出這部分影響已經很小了。