網上不少講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
/**
* 防止屢次點擊--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=======長按了按鈕"));
複製代碼
長按事件,這個不用說了吧。服務器
/**
* 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);
}
});
複製代碼
/**
* 每隔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作輪詢請求 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=======拒絕");
}
}
});
複製代碼
以上的使用場景是在現有項目中,而項目架構搭建的初期涉及到的網絡封裝、統一錯誤預處理等因爲篇幅問題,要拿出來單獨寫了。網絡返回的數據通常狀況下是後臺封裝好的固定格式(好比錯誤碼、錯誤信息由後臺接口設定),這樣處理起來還簡單一點。可是有時候api返回的數據格式是原生的http響應格式,這樣封裝處理的話外面又要套一層response泛型類,處理起來稍微比第一種狀況複雜一點。下篇博客再寫吧。app
其餘項目中使用到的場景,遇到了會更新在本博客……異步
最後,國際慣例 貼出項目地址:點我點我查看demo,若是對你有幫助,麻煩動動小手start鼓勵一下,謝謝。