RxBus-實現EventBus之Sticky

wzgiceman發表於2019-02-27

背景

前期由於工作中需要將EventBus替換成RxBus,所以按照EventBus的用法封裝了一套自己的RxBus,基本滿足了使用,專案發出後有不少兄弟告訴我沒有EventBusSticky功能,所以得閒完善這個功能,同樣是按照EventBus3.0註解的方式去實現和呼叫

RxBus-實現EventBus之post


效果

RxBus-實現EventBus之Sticky
這裡寫圖片描述

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之前基本功能的封裝原理方能更加全面的理解一下的內容

原RxBus基本功能實現原理:EventBus完全一樣的RxBus

1.新增sticky註解

不懂註解的同學可以先看下之前我寫的兩瓶關於註解的部落格

Java-註解詳解

Android-註解詳解

這裡新增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在訂閱者訂閱時,會傳送其最近傳送的資料(如果此時還沒有收到任何資料,它會傳送一個預設值)。

所以只有ReplaySubjectBehaviorSubject具備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訊息功能

  • 註冊銷燬簡單化


原始碼

傳送門-GitHub專案地址-戳我


建議

如果你對這套封裝有任何的問題和建議歡迎加入QQ群告訴我!

相關文章