項目實戰之Rxjava、RxBinding在實際項目中的使用

前言

網上不少講rxjava入門的文章,講了什麼是rxjava以及一些高大上的操做符,可是真正在項目中使用的場景不多講,那本篇文章主要講一下rxjava在實際項目中的應用場景,rxjava結合rxbinding在實際項目中的使用姿式瞭解一下。由於rxbind2 自己依賴rxjava2,因此項目中引入rxbinding就能夠了,rxjava2就不用引入了。java

implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
複製代碼

引入完了就看一下經常使用的使用場景吧:git

一、優化搜索

基本上app裏都有搜索這個功能需求吧,監聽et的文本變化而後請求服務器拉取數據,若是不優化處理器的話,每次et的值發生變化都會請求服務器,在弱網環境下極可能出現數據錯亂的問題。若是不用rxjava來處理,各類timer會把人寫暈掉吧,那麼看看rxjava怎麼來優雅的處理:github

//優化搜索功能
    RxTextView.textChanges(mBinding.etSearch)
            // 跳過一開始et內容爲空時的搜索
            .skip(1)
            //debounce 在必定的時間內沒有操做就會發送事件
            .debounce(1000, TimeUnit.MILLISECONDS)
            //下面這兩個都是數據轉換
            //flatMap:當同時多個網絡請求訪問的時候,前面的網絡數據會覆蓋後面的網絡數據
            //switchMap:當同時多個網絡請求訪問的時候,會以最後一個發送請求爲準,前面網路數據會被最後一個覆蓋
            .switchMap(new Function<CharSequence, ObservableSource<List<String>>>() {
                @Override
                public ObservableSource<List<String>> apply(CharSequence charSequence) throws Exception {
                    String searchKey = charSequence.toString();
                    System.out.println("binding=======搜索內容:" + searchKey);
                    //這裏執行網絡操做,獲取數據
                    List<String> list = new ArrayList<String>();
                    list.add("小劉哥");
                    list.add("可愛多");

                    return Observable.just(list);
                }
            })
            // .onErrorResumeNext()
            //網絡操做,獲取咱們須要的數據
            .subscribeOn(Schedulers.io())
            //界面更新在主線程
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Consumer<List<String>>() {
                @Override
                public void accept(List<String> strings) throws Exception {
                    System.out.println("binding=======搜索到" + strings.size() + "條數據");
                }
            });
複製代碼

註釋寫的很清楚了,不用解釋了吧,須要注意的一點就是 .skip(1) 這個操做符不能少,否則頁面一打開就會執行一次搜索的。api

二、結合rxbinding防手抖

/**
     * 防止屢次點擊--2秒內執行一次點擊
     */
    RxView.clicks(mBinding.btClick)
            .throttleFirst(2, TimeUnit.SECONDS)
            .subscribe(c -> System.out.println("binding=======點擊了按鈕"));
複製代碼

假如一個頁面有一個按鈕,點擊一次要請求一下服務器或者其餘操做均可以,這裏作了2秒內響應一次點擊事件,很經常使用的場景。緩存

三、長按事件

/**
     * 長按事件
     */
    RxView.longClicks(mBinding.btClick)
            .subscribe(c->System.out.println("binding=======長按了按鈕"));
複製代碼

長按事件,這個不用說了吧。服務器

四、監聽view的選中狀態

/**
     * checkbox 選中就修改textview
     */
    RxCompoundButton.checkedChanges(mBinding.checkbox)
            .subscribe(new Consumer<Boolean>() {
                @Override
                public void accept(Boolean aBoolean) throws Exception {
                    mBinding.tvCb.setText(aBoolean ? "按鈕選中了" : "按鈕未選中");
                }
            });
複製代碼

假如頁面有一個cb,好比選中表示贊成閱讀了用戶協議什麼的,用來監聽選中狀態來作一些邏輯操做,幾行代碼就搞定。網絡

五、註冊、登陸等獲取驗證碼時的倒計時操做

/**
 * 倒計時操做
 */
public void clickTimer(View view) {

    // 2 秒後發送數據
    Observable.timer(2, TimeUnit.SECONDS)
            .subscribe(new Observer<Long>() {
                @Override
                public void onSubscribe(Disposable d) {

                }

                @Override
                public void onNext(Long value) {
                    System.out.println("binding=======value:" + value);//0
                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onComplete() {

                }
            });

    //倒計時操做
    final int count = 10;
    Observable.interval(0, 1, TimeUnit.SECONDS)//設置0延遲,每隔一秒發送一條數據
            .take(count + 1)//設置循環次數
            .map(new Function<Long, Long>() {
                @Override
                public Long apply(Long aLong) throws Exception {

                    return count - aLong;
                }
            })
            .doOnSubscribe(new Consumer<Disposable>() {
                @Override
                public void accept(Disposable disposable) throws Exception {
                    //在發送數據的時候設置爲不能點擊
                    mBinding.btCutdown.setEnabled(false);

                    //背景色
                    mBinding.btCutdown.setBackgroundColor(Color.parseColor("#39c6c1"));
                }
            })
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<Long>() {
                @Override
                public void onSubscribe(Disposable d) {

                }

                @Override
                public void onNext(Long value) {
                    mBinding.btCutdown.setText("" + value);
                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onComplete() {
                    mBinding.btCutdown.setText("從新獲取");
                    mBinding.btCutdown.setEnabled(true);
                    mBinding.btCutdown.setBackgroundColor(Color.parseColor("#d1d1d1"));
                }
            });

}
複製代碼

很簡潔吧架構

六、註冊登陸等狀況下,全部輸入都合法再點亮登陸按鈕

/**
     * 註冊登陸等狀況下,全部輸入都合法再點亮登陸按鈕
     */
    Observable<CharSequence> name = RxTextView.textChanges(mBinding.etName).skip(1);
    Observable<CharSequence> age = RxTextView.textChanges(mBinding.etAge).skip(1);

    Observable.combineLatest(name, age, new BiFunction<CharSequence, CharSequence, Boolean>() {
        @Override
        public Boolean apply(CharSequence charSequence, CharSequence charSequence2) throws Exception {

            boolean isNameEmpty = TextUtils.isEmpty(mBinding.etName.getText());
            boolean isAgeEmpty = TextUtils.isEmpty(mBinding.etAge.getText());

            return !isNameEmpty && !isAgeEmpty;
        }
    })
            .subscribe(new Consumer<Boolean>() {
                @Override
                public void accept(Boolean aBoolean) throws Exception {
                    System.out.println("bt======" + aBoolean);
                    mBinding.btSubmit.setEnabled(aBoolean);
                }
            });
複製代碼

七、使用interval作週期性操做

/**
 * 每隔2秒 輸出一第二天志
 */
Disposable mDisposable;
public void clickIntervar(View view) {

    Observable.interval(2, TimeUnit.SECONDS)
            .subscribe(new Observer<Long>() {
                @Override
                public void onSubscribe(Disposable d) {
                    mDisposable =d;

                }

                @Override
                public void onNext(Long value) {
                    System.out.println("binding=======輸出日誌:" + value);
                    if (value == 5L) {
                        System.out.println("binding=======dispose");
                        mDisposable.dispose();
                    }
                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onComplete() {

                }
            });
}
複製代碼

八、使用schedulePeriodically作輪詢請求

/**
 * 使用schedulePeriodically作輪詢請求 3秒輪詢一次
 */
Observable.create(new ObservableOnSubscribe<String>() {
        @Override
        public void subscribe(final ObservableEmitter<String> e) throws Exception {

            Schedulers.newThread().createWorker()
                    .schedulePeriodically(new Runnable() {
                        @Override
                        public void run() {
                            e.onNext("net work-----");
                        }
                    }, 0, 3, TimeUnit.SECONDS);

        }
    }).subscribe(new Consumer<String>() {
        @Override
        public void accept(String s) throws Exception {
            System.out.println("binding=======net work");
        }
    });
複製代碼

九、網絡出錯重試

/**
 * 網絡錯誤重試
 * 這裏just操做符 改成retrofit 網絡請求返回的便可。
 */
int mRetryCount;

public void clickRetry(View view) {
    Observable.just("retry")
            .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
                @Override
                public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {

                    // 參數Observable<Throwable>中的泛型 = 上游操做符拋出的異常,可經過該條件來判斷異常的類型
                    return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                        @Override
                        public ObservableSource<?> apply(Throwable throwable) throws Exception {

                            // 判斷異常信息  根據異常信息判斷是否須要重試
                            if (throwable instanceof IOException) {
                                System.out.println("retry======y==");
                                // 重試
                                // 判斷重試次數 這裏設置最多重試5次
                                if (mRetryCount < 5) {
                                    mRetryCount++;
                                    /**
                                     * 一、經過返回的Observable發送的事件 = Next事件,從而使得retryWhen()重訂閱,最終實現重試功能
                                     * 二、延遲1段時間再重試  採用delay操做符 = 延遲一段時間發送,以實現重試間隔設置
                                     * 三、在delay操做符的等待時間內設置 = 每重試1次,增多延遲重試時間1s
                                     */
                                    int time = 1000 + mRetryCount * 1000;
                                    return Observable.just(1).delay(time, TimeUnit.MILLISECONDS);
                                } else {
                                    System.out.println("retry======5==");
                                    return Observable.error(new Throwable("已重試5次 放棄治療"));
                                }

                            } else {
                                // 不重試
                                System.out.println("retry======n==");
                                return Observable.error(new Throwable("發生了非網絡異常(非I/O異常)"));
                            }
                        }
                    });
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<String>() {
                @Override
                public void onSubscribe(Disposable d) {

                }

                @Override
                public void onNext(String value) {
                    System.out.println("retry======suc==" + value);
                }

                @Override
                public void onError(Throwable e) {
                    System.out.println("retry======err==" + e.toString());
                }

                @Override
                public void onComplete() {

                }
            });

}
複製代碼

十、解決網絡嵌套請求

/**
 * 優化網絡嵌套請求問題
 * 如下爲了方便演示 寫的僞代碼
 */
public void clickRequest(View view) {
    Observable<String> requestLogin = Observable.just("requestLogin");
    final Observable<String> request2 = Observable.just("request2");

    requestLogin.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnNext(new Consumer<String>() {
                @Override
                public void accept(String s) throws Exception {
                    System.out.println("flat=======loginsuccess");
                }
            })
            .observeOn(Schedulers.io())
            .flatMap(new Function<String, ObservableSource<String>>() {
                @Override
                public ObservableSource<String> apply(String s) throws Exception {
                    // 將網絡請求1轉換成網絡請求2,即發送網絡請求2
                    return request2;
                }
            })
            // (新被觀察者,同時也是新觀察者)切換到IO線程去發起登陸請求
            //  特別注意:由於flatMap是對初始被觀察者做變換,因此對於舊被觀察者,它是新觀察者,因此經過observeOn切換線程
            // 但對於初始觀察者,它則是新的被觀察者
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Consumer<String>() {
                @Override
                public void accept(String s) throws Exception {
                    System.out.println("flat=======第二次請求成功");
                }
            }, new Consumer<Throwable>() {
                @Override
                public void accept(Throwable throwable) throws Exception {
                    System.out.println("flat=======loginerr");
                }
            });
}
複製代碼

十一、背壓--這個就是記錄一下

/**
 * 背壓 Flowable g觀察者使用
 * 解決發送和訂閱事件 流速不一致的問題
 * <p>
 * 注意:同步訂閱中,被觀察者 & 觀察者工做於同1線程,同步訂閱關係中沒有緩存區。
 * 被觀察者在發送1個事件後,必須等待觀察者接收後,才能繼續發下1個事件.若Subscription.request沒有設置,
 * 觀察者接收不到事件,會拋出MissingBackpressureException異常。
 */
Subscription mSubscription;

public void clickFlow(View view) {

    Flowable.create(new FlowableOnSubscribe<Integer>() {
        @Override
        public void subscribe(FlowableEmitter<Integer> e) throws Exception {

            /**
             * 同步訂閱:
             * 同步訂閱的狀況下,調用e.requested()方法,獲取當前觀察者須要接收的事件數量.
             * 根據當前觀察者須要接收的事件數量來發送事件
             *
             * 異步訂閱:
             * 因爲兩者處於不一樣線程,因此被觀察者 沒法經過 FlowableEmitter.requested()知道觀察者自身接收事件能力。
             * 異步的反向控制:
             */
            long count = e.requested();
            System.out.println("flowable======須要接收的事件數量=" + count);

            e.onNext(1);
            e.onNext(2);
            e.onNext(3);
            e.onNext(4);
            e.onNext(5);
            e.onComplete();
        }
    }, BackpressureStrategy.ERROR)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<Integer>() {
                @Override
                public void onSubscribe(Subscription s) {

                    // 做用:決定觀察者可以接收多少個事件,多出的事件放入緩存區.若不設置,則不接收事件.
                    // 不過被觀察者仍然在發送事件(存放在緩存區,大小爲128),等觀察者須要時 再取出被觀察者事件(好比點擊事件裏).
                    // 可是 當緩存區滿時  就會溢出報錯
                    // 官方默認推薦使用Long.MAX_VALUE,即s.request(Long.MAX_VALUE);
                    mSubscription = s;
                    s.request(2);
                    // s.request(1); // 同步訂閱 觀察者連續要求接收事件的話,被觀察者e.requested() 返回3
                }

                @Override
                public void onNext(Integer integer) {

                    System.out.println("flowable=======" + integer);
                }

                @Override
                public void onError(Throwable t) {

                }

                @Override
                public void onComplete() {

                }
            });

}
複製代碼

十二、補充一個動態權限

添加依賴 compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.5@aar'

// 記得危險權限 清單文件裏也須要配置。
// 由於各個業務組件均可能使用到危險權限,我把權限統一寫在了commonLibrary裏
 RxPermissions permissions = new RxPermissions(this);
    RxView.clicks(mBinding.btPermission)
            .throttleFirst(1, TimeUnit.SECONDS)
            .subscribeOn(AndroidSchedulers.mainThread())
            .compose(permissions.ensure(Manifest.permission.CAMERA))
            .subscribe(new Consumer<Boolean>() {
                @Override
                public void accept(Boolean aBoolean) throws Exception {
                    if (aBoolean) {
                        System.out.println("binding=======容許");
                    } else {
                        System.out.println("binding=======拒絕");
                    }
                }
            });
複製代碼

1三、結合retrofit網絡請求封裝、統一錯誤預處理等

以上的使用場景是在現有項目中,而項目架構搭建的初期涉及到的網絡封裝、統一錯誤預處理等因爲篇幅問題,要拿出來單獨寫了。網絡返回的數據通常狀況下是後臺封裝好的固定格式(好比錯誤碼、錯誤信息由後臺接口設定),這樣處理起來還簡單一點。可是有時候api返回的數據格式是原生的http響應格式,這樣封裝處理的話外面又要套一層response泛型類,處理起來稍微比第一種狀況複雜一點。下篇博客再寫吧。app

其餘項目中使用到的場景,遇到了會更新在本博客……異步

最後,國際慣例 貼出項目地址:點我點我查看demo,若是對你有幫助,麻煩動動小手start鼓勵一下,謝謝。

相關文章
相關標籤/搜索