一、示例
1.1 應用場景
幾乎每個應用程式都提供了搜尋功能,某些應用還提供了搜尋聯想。對於一個搜尋聯想功能,最基本的實現流程為:客戶端通過EditText
的addTextChangedListener
方法監聽輸入框的變化,當輸入框發生變化之後就會回撥afterTextChanged
方法,客戶端利用當前輸入框內的文字向伺服器發起請求,伺服器返回與該搜尋文字關聯的結果給客戶端進行展示。
在該場景下,有幾個可以優化的方面:
- 在使用者連續輸入的情況下,可能會發起某些不必要的請求。例如使用者輸入了
abc
,那麼按照上面的實現,客戶端就會發起a
、ab
、abc
三個請求。 - 當搜尋詞為空時,不應該發起請求。
- 如果使用者依次輸入了
ab
和abc
,那麼首先會發起關鍵詞為ab
請求,之後再發起abc
的請求,但是abc
的請求如果先於ab
的請求返回,那麼就會造成使用者期望搜尋的結果為abc
,最終展現的結果卻是和ab
關聯的。
1.2 示例程式碼
這裡,我們針對上面提到的三個問題,使用RxJava2
提供的三個操作符進行了優化:
- 使用
debounce
操作符,當輸入框發生變化時,不會立刻將事件傳送給下游,而是等待200ms
,如果在這段事件內,輸入框沒有發生變化,那麼才傳送該事件;反之,則在收到新的關鍵詞後,繼續等待200ms
。 - 使用
filter
操作符,只有關鍵詞的長度大於0
時才傳送事件給下游。 - 使用
switchMap
操作符,這樣當發起了abc
的請求之後,即使ab
的結果返回了,也不會傳送給下游,從而避免了出現前面介紹的搜尋詞和聯想結果不匹配的問題。
public class SearchActivity extends AppCompatActivity {
private EditText mEtSearch;
private TextView mTvSearch;
private PublishSubject<String> mPublishSubject;
private DisposableObserver<String> DisposableObserver;
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();
DisposableObserver = 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(DisposableObserver);
mCompositeDisposable = new CompositeDisposable();
mCompositeDisposable.add(mCompositeDisposable);
}
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", "開始請求,關鍵詞為:" + query);
try {
Thread.sleep(100 + (long) (Math.random() * 500));
} catch (InterruptedException e) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(e);
}
}
Log.d("SearchActivity", "結束請求,關鍵詞為:" + query);
observableEmitter.onNext("完成搜尋,關鍵詞為:" + query);
observableEmitter.onComplete();
}
}).subscribeOn(Schedulers.io());
}
@Override
protected void onDestroy() {
super.onDestroy();
mCompositeDisposable.clear();
}
}
複製程式碼
執行結果為:
二、示例解析
下面,我們就來詳細的介紹一下這個例子中應用到的三種操作符
2.1 debounce
debounce
的原理圖如下所示:
debounce
原理類似於我們在收到請求之後,傳送一個延時訊息給下游,如果在這段延時時間內沒有收到新的請求,那麼下游就會收到該訊息;而如果在這段延時時間內收到來新的請求,那麼就會取消之前的訊息,並重新傳送一個新的延時訊息,以此類推。
而如果在這段時間內,上游傳送了onComplete
訊息,那麼即使沒有到達需要等待的時間,下游也會立刻收到該訊息。
2.2 filter
filter
的原理圖如下所示:
filter
的原理很簡單,就是傳入一個Predicate
函式,其引數為上游傳送的事件,只有該函式返回true
時,才會將事件傳送給下游,否則就丟棄該事件。
2.3 switchMap
switchMap
的原理是將上游的事件轉換成一個或多個新的Observable
,但是有一點很重要,就是如果在該節點收到一個新的事件之後,那麼如果之前收到的時間所產生的Observable
還沒有傳送事件給下游,那麼下游就再也不會收到它傳送的事件了。
如上圖所示,該節點先後收到了紅、綠、藍三個事件,並將它們對映成為紅一、紅二、綠一、綠二、藍一、藍二,但是當藍一傳送完事件時,綠二依舊沒有傳送事件,而最初綠色事件在藍色事件之前,那麼綠二就不會傳送給下游。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/