RxJava2 實戰(zhàn)知識梳理(3) - 優(yōu)化搜索聯(lián)想功能

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)流程為:客戶端通過EditTextaddTextChangedListener方法監(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屋灌、ababc三個請求应狱。
  • 當(dāng)搜索詞為空時共郭,不應(yīng)該發(fā)起請求。
  • 如果用戶依次輸入了ababc疾呻,那么首先會發(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 知識梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末婆跑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子庭呜,更是在濱河造成了極大的恐慌滑进,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件募谎,死亡現(xiàn)場離奇詭異扶关,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)数冬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門节槐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拐纱,你說我怎么就攤上這事铜异。” “怎么了秸架?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵揍庄,是天一觀的道長。 經(jīng)常有香客問我东抹,道長蚂子,這世上最難降的妖魔是什么沃测? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮食茎,結(jié)果婚禮上芽突,老公的妹妹穿的比我還像新娘。我一直安慰自己董瞻,他們只是感情好寞蚌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钠糊,像睡著了一般挟秤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抄伍,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天艘刚,我揣著相機(jī)與錄音,去河邊找鬼截珍。 笑死攀甚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的岗喉。 我是一名探鬼主播秋度,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钱床!你這毒婦竟也來了荚斯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤查牌,失蹤者是張志新(化名)和其女友劉穎事期,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纸颜,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兽泣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了胁孙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唠倦。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浊洞,靈堂內(nèi)的尸體忽然破棺而出牵敷,到底是詐尸還是另有隱情,我是刑警寧澤法希,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布枷餐,位于F島的核電站,受9級特大地震影響苫亦,放射性物質(zhì)發(fā)生泄漏毛肋。R本人自食惡果不足惜怨咪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望润匙。 院中可真熱鬧诗眨,春花似錦、人聲如沸孕讳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厂财。三九已至芋簿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間璃饱,已是汗流浹背与斤。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荚恶,地道東北人撩穿。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像谒撼,于是被迫代替她去往敵國和親食寡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容