RxJava2 實戰(zhàn)系列文章
RxJava2 實戰(zhàn)知識梳理(1) - 后臺執(zhí)行耗時操作,實時通知 UI 更新
RxJava2 實戰(zhàn)知識梳理(2) - 計算一段時間內(nèi)數(shù)據(jù)的平均值
RxJava2 實戰(zhàn)知識梳理(3) - 優(yōu)化搜索聯(lián)想功能
RxJava2 實戰(zhàn)知識梳理(4) - 結(jié)合 Retrofit 請求新聞資訊
RxJava2 實戰(zhàn)知識梳理(5) - 簡單及進(jìn)階的輪詢操作
RxJava2 實戰(zhàn)知識梳理(6) - 基于錯誤類型的重試請求
RxJava2 實戰(zhàn)知識梳理(7) - 基于 combineLatest 實現(xiàn)的輸入表單驗證
RxJava2 實戰(zhàn)知識梳理(8) - 使用 publish + merge 優(yōu)化先加載緩存徐紧,再讀取網(wǎng)絡(luò)數(shù)據(jù)的請求過程
RxJava2 實戰(zhàn)知識梳理(9) - 使用 timer/interval/delay 實現(xiàn)任務(wù)調(diào)度
RxJava2 實戰(zhàn)知識梳理(10) - 屏幕旋轉(zhuǎn)導(dǎo)致 Activity 重建時恢復(fù)任務(wù)
RxJava2 實戰(zhàn)知識梳理(11) - 檢測網(wǎng)絡(luò)狀態(tài)并自動重試請求
RxJava2 實戰(zhàn)知識梳理(12) - 實戰(zhàn)講解 publish & replay & share & refCount & autoConnect
RxJava2 實戰(zhàn)知識梳理(13) - 如何使得錯誤發(fā)生時不自動停止訂閱關(guān)系
RxJava2 實戰(zhàn)知識梳理(14) - 在 token 過期時朦促,刷新過期 token 并重新發(fā)起請求
RxJava2 實戰(zhàn)知識梳理(15) - 實現(xiàn)一個簡單的 MVP + RxJava + Retrofit 應(yīng)用
一拆檬、示例
1.1 應(yīng)用場景
幾乎每個應(yīng)用程序都提供了搜索功能惜傲,某些應(yīng)用還提供了搜索聯(lián)想。對于一個搜索聯(lián)想功能届良,最基本的實現(xiàn)流程為:客戶端通過EditText
的addTextChangedListener
方法監(jiān)聽輸入框的變化笆凌,當(dāng)輸入框發(fā)生變化之后就會回調(diào)afterTextChanged
方法,客戶端利用當(dāng)前輸入框內(nèi)的文字向服務(wù)器發(fā)起請求士葫,服務(wù)器返回與該搜索文字關(guān)聯(lián)的結(jié)果給客戶端進(jìn)行展示乞而。
在該場景下,有幾個可以優(yōu)化的方面:
- 在用戶連續(xù)輸入的情況下慢显,可能會發(fā)起某些不必要的請求爪模。例如用戶輸入了
abc
,那么按照上面的實現(xiàn)荚藻,客戶端就會發(fā)起a
屋灌、ab
、abc
三個請求应狱。 - 當(dāng)搜索詞為空時共郭,不應(yīng)該發(fā)起請求。
- 如果用戶依次輸入了
ab
和abc
疾呻,那么首先會發(fā)起關(guān)鍵詞為ab
請求除嘹,之后再發(fā)起abc
的請求,但是abc
的請求如果先于ab
的請求返回岸蜗,那么就會造成用戶期望搜索的結(jié)果為abc
尉咕,最終展現(xiàn)的結(jié)果卻是和ab
關(guān)聯(lián)的。
1.2 示例代碼
這里散吵,我們針對上面提到的三個問題龙考,使用RxJava2
提供的三個操作符進(jìn)行了優(yōu)化:
- 使用
debounce
操作符蟆肆,當(dāng)輸入框發(fā)生變化時,不會立刻將事件發(fā)送給下游晦款,而是等待200ms
炎功,如果在這段事件內(nèi),輸入框沒有發(fā)生變化缓溅,那么才發(fā)送該事件蛇损;反之,則在收到新的關(guān)鍵詞后坛怪,繼續(xù)等待200ms
淤齐。 - 使用
filter
操作符,只有關(guān)鍵詞的長度大于0
時才發(fā)送事件給下游袜匿。 - 使用
switchMap
操作符更啄,這樣當(dāng)發(fā)起了abc
的請求之后,即使ab
的結(jié)果返回了居灯,也不會發(fā)送給下游祭务,從而避免了出現(xiàn)前面介紹的搜索詞和聯(lián)想結(jié)果不匹配的問題。
public class SearchActivity extends AppCompatActivity {
private EditText mEtSearch;
private TextView mTvSearch;
private PublishSubject<String> mPublishSubject;
private DisposableObserver<String> mDisposableObserver;
private CompositeDisposable mCompositeDisposable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
mEtSearch = (EditText) findViewById(R.id.et_search);
mTvSearch = (TextView) findViewById(R.id.tv_search_result);
mEtSearch.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
startSearch(s.toString());
}
});
mPublishSubject = PublishSubject.create();
mDisposableObserver = new DisposableObserver<String>() {
@Override
public void onNext(String s) {
mTvSearch.setText(s);
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onComplete() {
}
};
mPublishSubject.debounce(200, TimeUnit.MILLISECONDS).filter(new Predicate<String>() {
@Override
public boolean test(String s) throws Exception {
return s.length() > 0;
}
}).switchMap(new Function<String, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(String query) throws Exception {
return getSearchObservable(query);
}
}).observeOn(AndroidSchedulers.mainThread()).subscribe(mDisposableObserver);
mCompositeDisposable = new CompositeDisposable();
mCompositeDisposable.add(mDisposableObserver);
}
private void startSearch(String query) {
mPublishSubject.onNext(query);
}
private Observable<String> getSearchObservable(final String query) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> observableEmitter) throws Exception {
Log.d("SearchActivity", "開始請求怪嫌,關(guān)鍵詞為:" + query);
try {
Thread.sleep(100 + (long) (Math.random() * 500));
} catch (InterruptedException e) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(e);
}
}
Log.d("SearchActivity", "結(jié)束請求义锥,關(guān)鍵詞為:" + query);
observableEmitter.onNext("完成搜索,關(guān)鍵詞為:" + query);
observableEmitter.onComplete();
}
}).subscribeOn(Schedulers.io());
}
@Override
protected void onDestroy() {
super.onDestroy();
mCompositeDisposable.clear();
}
}
運(yùn)行結(jié)果為:
二岩灭、示例解析
下面拌倍,我們就來詳細(xì)的介紹一下這個例子中應(yīng)用到的三種操作符
2.1 debounce
debounce
的原理圖如下所示:
debounce
原理類似于我們在收到請求之后,發(fā)送一個延時消息給下游噪径,如果在這段延時時間內(nèi)沒有收到新的請求柱恤,那么下游就會收到該消息;而如果在這段延時時間內(nèi)收到來新的請求熄云,那么就會取消之前的消息膨更,并重新發(fā)送一個新的延時消息,以此類推缴允。
而如果在這段時間內(nèi)荚守,上游發(fā)送了onComplete
消息,那么即使沒有到達(dá)需要等待的時間练般,下游也會立刻收到該消息矗漾。
2.2 filter
filter
的原理圖如下所示:
filter
的原理很簡單,就是傳入一個Predicate
函數(shù)薄料,其參數(shù)為上游發(fā)送的事件敞贡,只有該函數(shù)返回true
時,才會將事件發(fā)送給下游摄职,否則就丟棄該事件誊役。
2.3 switchMap
switchMap
的原理是將上游的事件轉(zhuǎn)換成一個或多個新的Observable
获列,但是有一點(diǎn)很重要,就是如果在該節(jié)點(diǎn)收到一個新的事件之后蛔垢,那么如果之前收到的時間所產(chǎn)生的Observable
還沒有發(fā)送事件給下游击孩,那么下游就再也不會收到它發(fā)送的事件了。
如上圖所示鹏漆,該節(jié)點(diǎn)先后收到了紅巩梢、綠、藍(lán)三個事件艺玲,并將它們映射成為紅一括蝠、紅二、綠一饭聚、綠二忌警、藍(lán)一、藍(lán)二若治,但是當(dāng)藍(lán)一發(fā)送完事件時慨蓝,綠二依舊沒有發(fā)送事件感混,而最初綠色事件在藍(lán)色事件之前端幼,那么綠二就不會發(fā)送給下游。
更多文章弧满,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.reibang.com/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結(jié)目錄:http://lizejun.cn/categories/