背景
前期由於工作中需要將EventBus
替換成RxBus
,所以按照EventBus
的用法封裝了一套自己的RxBus
,基本滿足了使用,專案發出後有不少兄弟告訴我沒有EventBus
的Sticky
功能,所以得閒完善這個功能,同樣是按照EventBus3.0
註解的方式去實現和呼叫
效果
sticky是什麼
在Android開發中,Sticky事件只指事件消費者在事件釋出之後才註冊的也能接收到該事件的特殊型別。Android中就有這樣的例項,也就是Sticky Broadcast,即粘性廣播。正常情況下如果傳送者傳送了某個廣播,而接收者在這個廣播傳送後才註冊自己的Receiver,這時接收者便無法接收到剛才的廣播,為此Android引入了StickyBroadcast,在廣播傳送結束後會儲存剛剛傳送的廣播(Intent),這樣當接收者註冊完Receiver後就可以接收到剛才已經發布的廣播。這就使得我們可以預先處理一些事件,讓有消費者時再把這些事件投遞給消費者。
使用
完全按照EventBus3.0版本的註解的方式去使用
- 傳送訊息
RxBus.getDefault().post(new EventStickText("我是sticky訊息"));複製程式碼
- 接收訊息
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void event(EventStickText eventStickText) {
Observable.timer(1, TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(aLong -> {
textView.setText(eventStickText.getMsg());
});
}
@Override
protected void onStart() {
super.onStart();
RxBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
RxBus.getDefault().unRegister(this);
}複製程式碼
實現
本篇的實現原理是基於之前的RxBus
封裝的基礎上的完善,所以需要大致瞭解RxBus
之前基本功能的封裝原理方能更加全面的理解一下的內容
1.新增sticky
註解
不懂註解的同學可以先看下之前我寫的兩瓶關於註解的部落格
這裡新增boolean sticky()
的方法,並且預設指定引數false
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
int code() default -1;
ThreadMode threadMode() default ThreadMode.CURRENT_THREAD;
boolean sticky() default false;
}複製程式碼
2.將sticky
加入資料封裝類中
將資料新加入的註解sticky
加入到資料封裝類中,在最後分發事件時用於區分sticky
事件
public class SubscriberMethod {
public Method method;
public ThreadMode threadMode;
public Class<?> eventType;
public Object subscriber;
public int code;
public boolean sticky;
public SubscriberMethod(Object subscriber, Method method, Class<?> eventType, int code,ThreadMode threadMode,boolean sticky ) {
this.method = method;
this.threadMode = threadMode;
this.eventType = eventType;
this.subscriber = subscriber;
this.code = code;
this.sticky=sticky;
}
******
}複製程式碼
3.記錄post
事件分析
因為sticky
的特殊性,而自帶RxJava
提供給我們四種方式處理Subject分發
-
ReplaySubject
在訂閱者訂閱時,會傳送所有的資料給訂閱者,無論它們是何時訂閱的。 -
PublishSubject
只會給在訂閱者訂閱的時間點之後的資料傳送給觀察者。 -
AsyncSubject
只在原Observable
事件序列完成後,傳送最後一個資料,後續如果還有訂閱者繼續訂閱該Subject
, 則可以直接接收到最後一個值。 -
BehaviorSubject
在訂閱者訂閱時,會傳送其最近傳送的資料(如果此時還沒有收到任何資料,它會傳送一個預設值)。
所以只有ReplaySubject
和BehaviorSubject
具備Sticky
的特性。
但是:這兩種方式都不適合
-
BehaviorSubject
因為只是保留最近一次的事件,這樣會導致事件的覆蓋問題 -
ReplaySubject能解決BehaviorSubject的事件丟失的問題,能儲存所有的事件,但是分發起來確實一個難點,暫時沒有找到合適的處理方法
-
還有我們之前的封裝採用的
PublishSubject
的實現方式去分發RxBus
的事件,如果換成任何其他的分發機制都會導致sticky事件和正常事件資料需要獨立來做,成本太高
public RxBus() {
bus = new SerializedSubject<>(PublishSubject.create());
}複製程式碼
解決辦法:通過Map<事件型別,事件>手動記錄訊息事件,和PublishSubject
資料統一起來處理,避免速度的獨立,這裡選擇執行緒安全的ConcurrentHashMap
4.ConcurrentHashMap
記錄事件
初始化
/*stick資料*/
private final Map<Class<?>, Object> stickyEvent =new ConcurrentHashMap<>();複製程式碼
在post
方法中新增事件
/**
* 提供了一個新的事件,單一型別
*
* @param o 事件資料
*/
public void post(Object o) {
synchronized (stickyEvent) {
stickyEvent.put(o.getClass(), o);
}
bus.onNext(o);
}複製程式碼
5.通過sticky
註解得到Observable
物件
在register(Object subscriber)
方法中通過反射得到自定義註解的資料,然後放入到自定義的資料型別SubscriberMethod
中
/**
* 註冊
*
* @param subscriber 訂閱者
*/
public void register(Object subscriber) {
/*避免重複建立*/
if(eventTypesBySubscriber.containsKey(subscriber)){
return;
}
Class<?> subClass = subscriber.getClass();
Method[] methods = subClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Subscribe.class)) {
//獲得引數型別
Class[] parameterType = method.getParameterTypes();
//引數不為空 且引數個數為1
if (parameterType != null && parameterType.length == 1) {
Class eventType = parameterType[0];
addEventTypeToMap(subscriber, eventType);
Subscribe sub = method.getAnnotation(Subscribe.class);
int code = sub.code();
ThreadMode threadMode = sub.threadMode();
boolean sticky = sub.sticky();
SubscriberMethod subscriberMethod = new SubscriberMethod(subscriber, method, eventType, code, threadMode,
sticky);
addSubscriberToMap(eventType, subscriberMethod);
addSubscriber(subscriberMethod);
}
}
}
}複製程式碼
當事件觸發以後,通過SubscriberMethod
記錄的資料生成不同的Observable物件,現在對sticky訊息增加了響應的物件處理
/**
* 根據傳遞的 eventType 型別返回特定型別(eventType)的 被觀察者
*/
public <T> Observable<T> toObservableSticky(final Class<T> eventType) {
synchronized (stickyEvent) {
Observable<T> observable = bus.ofType(eventType);
final Object event = stickyEvent.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。
6.Observable
物件分發事件
得到sticky的壓縮Observable
物件後,還需要按照註解中被指定的執行緒去觸發事件任務
/**
* 用於處理訂閱事件在那個執行緒中執行
*
* @param observable
* @param subscriberMethod
* @return
*/
private Observable postToObservable(Observable observable, SubscriberMethod subscriberMethod) {
switch (subscriberMethod.threadMode) {
case MAIN:
observable.observeOn(AndroidSchedulers.mainThread());
break;
case NEW_THREAD:
observable.observeOn(Schedulers.newThread());
break;
case CURRENT_THREAD:
observable.observeOn(Schedulers.immediate());
break;
case IO:
observable.observeOn(Schedulers.io());
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscriberMethod.threadMode);
}
return observable;
}複製程式碼
這裡很簡單,直接運用RxJava和RxAndroid的執行緒去管理即可
7.sticky訊息的銷燬
因為這裡的sticky訊息採用的是map佇列記錄的方式去實現,所以當sticky訊息不在被需求記錄的時候,或者程式退出,需要手動清空map佇列的資料,避免記憶體溢位和浪費
/**
* 移除指定eventType的Sticky事件
*/
public <T> T removeStickyEvent(Class<T> eventType) {
synchronized (stickyEvent) {
return eventType.cast(stickyEvent.remove(eventType));
}
}
/**
* 移除所有的Sticky事件
*/
public void removeAllStickyEvents() {
synchronized (stickyEvent) {
stickyEvent.clear();
}
}複製程式碼
結果
通過sticky訊息的完善,RxBus已經完全實現了EventBus3.0的全部功能,並且全部安裝EventBus3.0的使用方法來封裝,方便專案的遷移和使用。
-
註解方式定義
-
post方式分發事件
-
sticky訊息功能
-
註冊銷燬簡單化