前言
網上很多講rxjava入門的文章,講了什麼是rxjava以及一些高大上的操作符,但是真正在專案中使用的場景很少講,那本篇文章主要講一下rxjava在實際專案中的應用場景,rxjava結合rxbinding在實際專案中的使用姿勢瞭解一下。因為rxbind2 本身依賴rxjava2,所以專案中引入rxbinding就可以了,rxjava2就不用引入了。
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
複製程式碼
引入完了就看一下常用的使用場景吧:
1、優化搜尋
基本上app裡都有搜尋這個功能需求吧,監聽et的文字變化然後請求伺服器拉取資料,如果不優化處理器的話,每次et的值發生變化都會請求伺服器,在弱網環境下很可能出現資料錯亂的問題。如果不用rxjava來處理,各種timer會把人寫暈掉吧,那麼看看rxjava怎麼來優雅的處理:
//優化搜尋功能
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) 這個操作符不能少,不然頁面一開啟就會執行一次搜尋的。
2、結合rxbinding防手抖
/**
* 防止多次點選--2秒內執行一次點選
*/
RxView.clicks(mBinding.btClick)
.throttleFirst(2, TimeUnit.SECONDS)
.subscribe(c -> System.out.println("binding=======點選了按鈕"));
複製程式碼
假如一個頁面有一個按鈕,點選一次要請求一下伺服器或者其他操作都可以,這裡做了2秒內響應一次點選事件,很常用的場景。
3、長按事件
/**
* 長按事件
*/
RxView.longClicks(mBinding.btClick)
.subscribe(c->System.out.println("binding=======長按了按鈕"));
複製程式碼
長按事件,這個不用說了吧。
4、監聽view的選中狀態
/**
* checkbox 選中就修改textview
*/
RxCompoundButton.checkedChanges(mBinding.checkbox)
.subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) throws Exception {
mBinding.tvCb.setText(aBoolean ? "按鈕選中了" : "按鈕未選中");
}
});
複製程式碼
假如頁面有一個cb,比如選中表示同意閱讀了使用者協議什麼的,用來監聽選中狀態來做一些邏輯操作,幾行程式碼就搞定。
5、註冊、登入等獲取驗證碼時的倒數計時操作
/**
* 倒數計時操作
*/
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"));
}
});
}
複製程式碼
很簡潔吧
6、註冊登入等情況下,所有輸入都合法再點亮登入按鈕
/**
* 註冊登入等情況下,所有輸入都合法再點亮登入按鈕
*/
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);
}
});
複製程式碼
7、使用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() {
}
});
}
複製程式碼
8、使用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");
}
});
複製程式碼
9、網路出錯重試
/**
* 網路錯誤重試
* 這裡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++;
/**
* 1、通過返回的Observable傳送的事件 = Next事件,從而使得retryWhen()重訂閱,最終實現重試功能
* 2、延遲1段時間再重試 採用delay操作符 = 延遲一段時間傳送,以實現重試間隔設定
* 3、在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() {
}
});
}
複製程式碼
10、解決網路巢狀請求
/**
* 優化網路巢狀請求問題
* 以下為了方便演示 寫的虛擬碼
*/
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");
}
});
}
複製程式碼
11、背壓--這個就是記錄一下
/**
* 背壓 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() {
}
});
}
複製程式碼
12、補充一個動態許可權
新增依賴 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=======拒絕");
}
}
});
複製程式碼
13、結合retrofit網路請求封裝、統一錯誤預處理等
以上的使用場景是在現有專案中,而專案架構搭建的初期涉及到的網路封裝、統一錯誤預處理等由於篇幅問題,要拿出來單獨寫了。網路返回的資料一般情況下是後臺封裝好的固定格式(比如錯誤碼、錯誤資訊由後臺介面設定),這樣處理起來還簡單一點。但是有時候api返回的資料格式是原生的http響應格式,這樣封裝處理的話外面又要套一層response泛型類,處理起來稍微比第一種情況複雜一點。下篇部落格再寫吧。
其他專案中使用到的場景,遇到了會更新在本部落格……
最後,國際慣例 貼出專案地址:點我點我檢視demo,如果對你有幫助,麻煩動動小手start鼓勵一下,謝謝。