RxBus、EventBus因為解耦太徹底,濫用的話,項目可維護性會越來越低;一些簡單情境更推薦用回調、Subject來代替事件匯流排。
實際使用情境,如果RxBus,EventBus二選一,我更傾向於使用EventBus, RxJava專註工作流程,EventBus專註事件匯流排,職責更清晰
有段時間沒更了,幾個月前,我寫過一篇實現簡單的RxBus文章: 用RxJava實現事件匯流排。
在實際環境中,你會發現RxBus還是有一些問題的。 你需要RxBus支援Sticky功能。 你會發現在你訂閱了某個事件後,在後續接收到該事件時,處理的過程中發生了異常,你可能會發現後續的事件都接收不到了。
我將分2篇文章分別給出其方案,這篇介紹如何?Sticky,另外一篇介紹RxBus中的異常處理方案:
深入RxBus:[異常處理] 什麼是Sticky事件。
在Android開發中,Sticky事件只指事件消費者在事件發布之後才註冊的也能接收到該事件的特殊類型。Android中就有這樣的執行個體,也就是Sticky Broadcast,即粘性廣播。正常情況下如果寄件者發送了某個廣播,而接收者在這個廣播發送後才註冊自己的Receiver,這時接收者便無法接收到剛才的廣播,為此Android引入了StickyBroadcast,在廣播發送結束後會儲存剛剛發送的廣播(Intent),這樣當接收者註冊完Receiver後就可以接收到剛才已經發布的廣播。這就使得我們可以預先處理一些事件,讓有消費者時再把這些事件投遞給消費者。 Subject
我們在實現簡單的RxBus時使用了PublishSubject,其實RxJava提供給開發人員4種Subject:
PublishSubject,BehaviorSubject ,BehaviorSubject,AsyncSubject。 PublishSubject只會給在訂閱者訂閱的時間點之後的資料發送給觀察者。
BehaviorSubject在訂閱者訂閱時,會發送其最近發送的資料(如果此時還沒有收到任何資料,它會發送一個預設值)。
ReplaySubject在訂閱者訂閱時,會發送所有的資料給訂閱者,無論它們是何時訂閱的。
AsyncSubject只在原Observable事件序列完成後,發送最後一個資料,後續如果還有訂閱者繼續訂閱該Subject, 則可以直接接收到最後一個值。 Subject
從上圖來看,似乎BehaviorSubject和ReplaySubject具備Sticky的特性。 BehaviorSubject方案
BehaviorSubject似乎完全符合Sticky的定義,但是你發現了它只能儲存最近的那個事件。
有這樣的情境:如果訂閱者A訂閱了Event1,訂閱者B訂閱了Event2,而此時BehaviorSubject事件隊列裡是[…, Event2, Event1],當訂閱者訂閱時,因為儲存的是最近的事件:即Event1,所以訂閱者B是接收不到Event2的。
解決辦法就是:
每個Event類型都各自建立一個對應的BehaviorSubject,這樣的話資源開銷比較大,並且該Sticky事件匯流排和普通的RxBus事件匯流排不能共用,即:普通事件和Sticky事件是獨立的,因為普通事件是基於PublishSubject的, 暫時放棄該方案。 ReplaySubject方案
ReplaySubject可以儲存發送過的所有資料事件。
因為儲存了所有的資料事件,所以不管什麼類型的Event,我們只要過濾該類型,並讓其發送最近的那個Event即可滿足Sticky事件了。但是擷取最近的對應事件是個痛點,因為最符合需求的操作符takeLast()僅在訂閱事件結束時(即:onCompleted())才會發送最後的那個資料事件,而我們的RxBus正常情況下應該是盡量避免其訂閱事件結束的。(我沒能找到合適的操作符,如果你知道,請告知我)
所以BehaviorSubject也是比較難實現Sticky特性的。
並且,不管是BehaviorSubject還是ReplaySubject,它們還有一個棘手的問題:它們實現的EventBus和普通的RxBus(基於PublishSubject)之間的資料是相互獨立的。
總結:BehaviorSubject和BehaviorSubject都不能天然適合Sticky事件…… 使用Map實現Sticky
該方法思路是在原來PublishSubject實現的RxBus基礎上,使用ConcurrentHashMap<事件類型,事件>儲存每個事件的最近事件,不僅能實現Sticky特性,最重要的是可以和普通RxBus的事件數目據共用,不獨立。
因為我們的RxBus是基於PublishSubject的,而RxJava又有4種Subject,而且其中的BehaviorSubject和ReplaySubject看起來又符合Sticky特性,所以我們可能會鑽這個牛角尖,理所當然的認為實現Sticky需要通過其他類型的Subject…. (好吧,我鑽進去了…)
這個方案的思路我是根據EventBus的實現想到的,下面是大致流程: Map的初始化:
private final Map<Class<?>, Object> mStickyEventMap; public RxBus() { mBus = new SerializedSubject<>(PublishSubject.create()); mStickyEventMap = new ConcurrentHashMap<>(); }
ConcurrentHashMap是一個安全執行緒的HashMap, 採用stripping lock(分離鎖),效率比HashTable高很多。 在我們postSticky(Event)時,存入Map中:
public void postSticky(Object event) { synchronized (mStickyEventMap) { mStickyEventMap.put(event.getClass(), event); } post(event); } 訂閱時toObservableSticky(Class<T> eventType),先從Map中尋找是否包含該類型的事件,如果沒有,則說明沒有Sticky事件要發送,直接訂閱Subject(此時作為被觀察者Observable);如果有,則說明有Sticky事件需要發送,訂閱merge(Subject 和 Sticky事件)。
public <T> Observable<T> toObservableSticky(final Class<T> eventType) { synchronized (mStickyEventMap) { Observable<T> observable = mBus.ofType(eventType); final Object event = mStickyEventMap.get(eventType); if (event != null) { return observable.mergeWith(Observable.create(new Observable.OnSubscribe<T>() { @Override public void call(Subscriber<? super T> subscriber) { subscriber.onNext(eventType.cast(event)); } })); } else { return observable; } } }
merge操作符:可以將多個Observables合并,就好像它們是單個的Observable一樣。
這樣,Sticky的核心功能就完成了,使用上和普通RxBus一樣,通過postSticky()發送事件,toObservableSticky()訂閱事件。
除此之外,我還提供了getStickyEvent(Class<T> eventType),removeStickyEvent(Class<T> eventType),removeAllStickyEvents()方法,供尋找、移除對應事件類型的事件、移除全部Sticky事件。 重要的事
在使用Sticky特性時,在不需要某Sticky事件時, 通過removeStickyEvent(Class<T> eventType)移除它,最保險的做法是:在主Activity的onDestroy裡removeAllStickyEvents()。
因為我們的RxBus是個單例靜態對象,再正常退出app時,該對象依然會存在於JVM,除非進程被殺死,這樣的話導致StickyMap裡的資料依然存在,為了避免該問題,需要在app退出時,清理StickyMap。
// 主Activity(一般是棧底Activity)@Overrideprotected void onDestroy() { super.onDestroy(); // 移除所有Sticky事件 RxBus.getDefault().removeAllStickyEvents();}
完整代碼
下面是支援Sticky的完整RxBus代碼:
/** * PublishSubject: 只會把在訂閱發生的時間點之後來自原始Observable的資料發射給觀察者 * <p> * Created by YoKeyword on 2015/6/17. */public class RxBus { private static volatile RxBus mDefaultInstance; private final Subject<Object, Object> mBus; private final Map<Class<?>, Object> mStickyEventMap; public RxBus() { mBus = new SerializedSubject<>(PublishSubject.create()); mStickyEventMap = new ConcurrentHashMap<>(); } public static RxBus getDefault() { if (mDefaultInstance == null) { synchronized (RxBus.class) { if (mDefaultInstance == null) { mDefaultInstance = new RxBus(); } } } return mDefaultInstance; } /** * 發送事件 */ public void post(Object event) { mBus.onNext(event); } /** * 根據傳遞的 eventType 類型返回特定類型(eventType)的 被觀察者 */ public <T> Observable<T> toObservable(Class<T> eventType) { return mBus.ofType(eventType); } /** * 判斷是否有訂閱者 */ public boolean hasObservers() { return mBus.hasObservers(); } public void reset() { mDefaultInstance = null; } /** * Stciky 相關 */ /** * 發送一個新Sticky事件 */ public void postSticky(Object event) { synchronized (mStickyEventMap) { mStickyEventMap.put(event.getClass(), event); } post(event); } /** * 根據傳遞的 eventType 類型返回特定類型(eventType)的 被觀察者 */ public <T> Observable<T> toObservableSticky(final Class<T> eventType) { synchronized (mStickyEventMap) { Observable<T> observable = mBus.ofType(eventType); final Object event = mStickyEventMap.get(eventType); if (event != null) { return observable.mergeWith(Observable.create(new Observable.OnSubscribe<T>() { @Override public void call(Subscriber<? super T> subscriber) { subscriber.onNext(eventType.cast(event)); } })); } else { return observable; } } } /** * 根據eventType擷取Sticky事件 */ public <T> T getStickyEvent(Class<T> eventType) { synchronized (mStickyEventMap) { return eventType.cast(mStickyEventMap.get(eventType)); } } /** * 移除指定eventType的Sticky事件 */ public <T> T removeStickyEvent(Class<T> eventType) { synchronized (mStickyEventMap) { return eventType.cast(mStickyEventMap.remove(eventType)); } } /** * 移除所有的Sticky事件 */ public void removeAllStickyEvents() { synchronized (mStickyEventMap) { mStickyEventMap.clear(); } }}
雖然使用了安全執行緒的ConCurrentHashMap,但是依然大量使用了synchronized,可以發現鎖住的是mStickyEventMap對象,這是為了保證對於讀、寫、查、刪同步,即讀時不能寫,寫時不能讀… 最後
附上一個Demo: 提供了使用Sticky特性的樣本 異常處理的樣本:讓其在發生異常後,仍能正確接收到後續的Event。
參考源碼,傳送門 Demo 參考:
ReactiveX: http://reactivex.io/
EventBus: https://github.com/greenrobot/EventBus
轉自: https://www.jianshu.com/p/71ab00a2677b