EventBus是一個很棒的工具,它可用來對程式元件進行解耦。在接下來的幾篇文章中我將會闡述如何通過它來簡化程式碼,提高可讀性以及簡化測試。但在本文中我想首先討論一下為什麼我會選擇使用EventBus。特別是我會將其與同類技術進行對比。
對比Java監聽器介面(Listener Interfaces)
在Java中,特別是Android,一個常用的模式就是使用”監聽器(Listeners)”介面。在此模式中,一個實現了監聽器介面的類必須將自身註冊到它想要監聽的類中去。這就意味著監聽與被監聽之間屬於強關聯關係。這種關係就使得單元測試很難進行開展。Jake Wharton在他的《使用Otto解耦Android應用間通訊》這篇文章中很好地闡述了這個問題,在此我就不進行展開了。
對比本地廣播管理器(LocalBroadcastManager)
另一項技術就是在元件間通過本地廣播管理器(LocalBroadcastManager)進行訊息的傳送與監聽。雖然這對於解耦有很好的幫助,但它的API不如EventBus那樣簡潔。此外,如果你不注意保持Intent extras型別的一致,它還可能引發潛在的執行時/型別檢測錯誤。
考慮下面的應用場景:
- 元件A執行一個批量非同步資料庫更新操作。當成功完成時,它傳送一條通知告訴更新了多少條記錄。發生錯誤時,傳送一條通知包含了錯誤資訊。
- 元件B則需要知道到更新是否完成和更新了多少條記錄,以及是否有錯誤發生和錯誤資訊。
使用LocalBroadcastManager,最簡單的程式碼大概如下面所示。
元件A:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// when update completes successfully Intent intent = new Intent(); intent.setAction(FILTER_ACTION_UPDATE_SUCCESS); intent.putExtra(EXTRA_COUNT, count); LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); ... // when there is an error Intent intent = new Intent(); intent.setAction(FILTER_ACTION_UPDATE_ERROR); intent.putExtra(EXTRA_ERROR_MSG, error.getMessage()); LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); |
元件B:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
IntentFilter updateSuccessFilter = new IntentFilter(FILTER_ACTION_UPDATE_SUCCESS); BroadcastReceiver updateSuccessReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int updateCount = intent.getIntExtra(EXTRA_COUNT, -1); // TODO - do something with the count } }; IntentFilter updateErrorFilter = new IntentFilter(FILTER_ACTION_UPDATE_ERROR); BroadcastReceiver updateErrorReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String errorMsg = intent.getStringExtra(EXTRA_ERROR_MSG); // TODO - display error message or similar } }; ... getContext().registerReceiver(updateSuccessReceiver, updateSuccessFilter); getContext().registerReceiver(updateErrorReceiver, updateErrorFilter); ... getContext().unregisterReceiver(updateSuccessReceiver); getContext().unregisterReceiver(updateErrorReceiver); |
使用EventBus,同樣功能實現可以表示如下。(注:此例中我使用的是我的EventBus實現,參見Green Robot的EventBus。)
Event類:
1 2 3 4 5 6 7 |
public class UpdateCompleteEvent { public final int count; UpdateCompleteEvent(int count){ this.count = count; } } |
1 2 3 4 5 6 7 |
public class UpdateErrorEvent { public final String message; UpdateCompleteEvent(String message){ this.message = message; } } |
元件A:
1 2 3 4 5 6 7 8 9 10 |
EventBus bus = EventBus.getDefault(); // Or better yet, inject it ... // when update completes successfully bus.post(new UpdateCompleteEvent(count)); ... // when there is an error bus.post(new UpdateErrorEvent(error.getMessage())); |
元件B:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
EventBus bus = EventBus.getDefault(); // Or better yet, inject it ... public void onEvent(UpdateCompleteEvent event){ // TODO - do something with event.count } public void onEvent(UpdateErrorEvent event){ // TODO - do something with event.message } ... bus.register(this); ... bus.unregister(this); |
如你所見,使用EventBus不僅使程式碼變得清晰,而且增強了型別安全(type-safe)。當用Intent傳遞資料時,在編譯時並不能檢查出所設的extra型別與收到時的型別一致。所以一個很常見的錯誤便是你或者你團隊中的其他人改變了Intent所傳遞的資料,但忘記了對全部的接收器(receiver)進行更新。這種錯誤在編譯時是無法被發現的,只有在執行時才會發現問題。
而使用EventBus所傳遞的訊息則是通過你所定義的Event類。由於接收者方法是直接與這些類例項打交道,所以所有的資料均可以進行型別檢查,這樣任何由於型別不一致所導致的錯誤都可以在編譯時刻被發現。
另外就是你的Event類可以定義成任何型別。我通常會為了表示事件而顯式地建立明確命名的類,你也通過EventBus傳送/接收任何類。通過這種方法,你就不必受限於那些只能新增到Intent extras中的簡單資料型別了。例如,你可以傳送一個和ORM(注1)模型類例項,並且在接收端直接處理與ORM操作相關的類例項。
希望我這裡給出了一個在Android中使用EventBus的強有力的應用場景案例。在下一篇文章中我會談談我是如何使用Green Robot的EventBus中的“sticky”事件的。
注1: ORM(Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping)物件關係對映,是一種程式設計技術,用於實現物件導向程式語言裡不同型別系統的資料之間的轉換。
Android中如何使用EventBus呢?請看這一系列文章 閱讀原文 »