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, 則可以直接接收到最后一個值。
從上圖來看,似乎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)
@Override
protected 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。
參考:
ReactiveX: http://reactivex.io/
EventBus: https://github.com/greenrobot/EventBus