1. 程式人生 > >RxJava 使用debounce操作符 優化app搜尋功能

RxJava 使用debounce操作符 優化app搜尋功能

問題

現在幾乎所有的App都有搜尋功能 , 一般情況我們監聽EditText控制元件,當值發生改變去請求搜尋介面. 如:

etKey.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) { String key = etKey.getText().toString().trim(); if (key.length() > 0){ search(key);// 請求搜尋介面,成功後把結果顯示到介面上. } } });

這樣做有兩個問題:

  1. 可能導致很多沒有意義的請求,耗費使用者流量(因為控制元件的值每更改一次立即就會去請求網路,而且只是最後輸入的關鍵字是有用的)
  2. 可能導致最終搜尋的結果不是使用者想要的. 例如,使用者一開始輸入關鍵字’AB’ 這個時候出現兩個請求, 一個請求是A關鍵字, 一個請求是AB關鍵字. 表面上是’A’請求先發出去, ‘AB’請求後發出去. 如果後發出去的’AB’請求先返回, ‘A’請求後返回,那麼’A’請求後的結果將會覆蓋’AB’請求的結果. 從而導致搜尋結果不正確.

解決問題

使用強大的RxJava的 debounce操作符 可以解決這個問題。

subscription = RxTextView.textChanges(etKey)
                .debounce(400, TimeUnit.MILLISECONDS
, AndroidSchedulers.mainThread()) .subscribeOn(AndroidSchedulers.mainThread())// 對etKey[EditText]的監聽操作 需要在主執行緒操作 //對使用者輸入的關鍵字進行過濾 .filter(new Func1<CharSequence, Boolean>() { @Override public Boolean call(CharSequence charSequence) { Log.d("RxJava", "filter is main thread : " + (Looper.getMainLooper() == Looper.myLooper())); return charSequence.toString().trim().length() > 0; } }) .flatMap(new Func1<CharSequence, Observable<List<String>>>() { @Override public Observable<List<String>> call(CharSequence charSequence) { Log.d("RxJava", getMainText("flatMap")); return searchApi.search(charSequence.toString()); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<List<String>>() { @Override public void call(List<String> strings) { tvContent.setText("search result:\n\n"); tvContent.append(strings.toString()); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { throwable.printStackTrace(); tvContent.append("Error:" + throwable.getMessage()); } });

上面程式碼的主要邏輯:

  • 使用debounce操作符設定: 只有當用戶輸入關鍵字後400毫秒才發射資料[說的直白點就是400毫秒後才會走後面的邏輯];
  • 使用filter操作符 對使用者輸入的關鍵字進行過濾:只有輸入的關鍵字不為空,才會走後面的邏輯;
  • 使用flatMap操作符:使用最終的關鍵字去請求搜尋介面

至此,避免EditText每改變一次就請求一次的情況。

但是,還有一個問題,上面說的導致搜尋結果的錯亂,上面的程式碼還是沒有解決,比如停止輸入400毫秒後, 那麼肯定會開始請求Search介面, 但是使用者又會輸入新的關鍵字,這個時候上個請求還沒有返回, 新的請求又去請求Search介面.這個時候有可能最後的一個請求返回, 第一個請求最後返回,導致最終顯示的結果是第一次搜尋的結果.

怎麼去解決這個問題:可以使用switchMap操作符解決。

看看官網對 switchMap操作符 如何解釋的:

Returns a new Observable by applying a function that you supply to each item emitted by the source Observable that returns an Observable, 
and then emitting the items emitted by the most recently emitted of these Observables.

switchMap操作符 和 flatMap操作符 差不多,區別是switchMap操作符只會發射[emit]最近的Observables。

也就是說,當400毫秒後,發出第一個搜尋請求,當這個請求的過程中,使用者又去搜索了,發出第二個請求,不管怎樣,switchMap操作符只會發射第二次請求的Observable。所以,在上面的程式碼基礎上把flatMap改成switchMap就可以了。

功能實用,真是越來越喜歡RxJava了。