RxJava2 實戰知識梳理(3) 優化搜尋聯想功能

澤毛發表於2017-12-21

一、示例

1.1 應用場景

幾乎每個應用程式都提供了搜尋功能,某些應用還提供了搜尋聯想。對於一個搜尋聯想功能,最基本的實現流程為:客戶端通過EditTextaddTextChangedListener方法監聽輸入框的變化,當輸入框發生變化之後就會回撥afterTextChanged方法,客戶端利用當前輸入框內的文字向伺服器發起請求,伺服器返回與該搜尋文字關聯的結果給客戶端進行展示。

在該場景下,有幾個可以優化的方面:

  • 在使用者連續輸入的情況下,可能會發起某些不必要的請求。例如使用者輸入了abc,那麼按照上面的實現,客戶端就會發起aababc三個請求。
  • 當搜尋詞為空時,不應該發起請求。
  • 如果使用者依次輸入了ababc,那麼首先會發起關鍵詞為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();
    }
}
複製程式碼

執行結果為:

RxJava2 實戰知識梳理(3)   優化搜尋聯想功能

二、示例解析

下面,我們就來詳細的介紹一下這個例子中應用到的三種操作符

2.1 debounce

debounce的原理圖如下所示:

RxJava2 實戰知識梳理(3)   優化搜尋聯想功能
debounce原理類似於我們在收到請求之後,傳送一個延時訊息給下游,如果在這段延時時間內沒有收到新的請求,那麼下游就會收到該訊息;而如果在這段延時時間內收到來新的請求,那麼就會取消之前的訊息,並重新傳送一個新的延時訊息,以此類推。

而如果在這段時間內,上游傳送了onComplete訊息,那麼即使沒有到達需要等待的時間,下游也會立刻收到該訊息。

2.2 filter

filter的原理圖如下所示:

RxJava2 實戰知識梳理(3)   優化搜尋聯想功能
filter的原理很簡單,就是傳入一個Predicate函式,其引數為上游傳送的事件,只有該函式返回true時,才會將事件傳送給下游,否則就丟棄該事件。

2.3 switchMap

RxJava2 實戰知識梳理(3)   優化搜尋聯想功能
switchMap的原理是將上游的事件轉換成一個或多個新的Observable,但是有一點很重要,就是如果在該節點收到一個新的事件之後,那麼如果之前收到的時間所產生的Observable還沒有傳送事件給下游,那麼下游就再也不會收到它傳送的事件了。

如上圖所示,該節點先後收到了紅、綠、藍三個事件,並將它們對映成為紅一、紅二、綠一、綠二、藍一、藍二,但是當藍一傳送完事件時,綠二依舊沒有傳送事件,而最初綠色事件在藍色事件之前,那麼綠二就不會傳送給下游。


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章