RxJava 使用debounce操作符 优化app搜索功能



1 抛出问题

现在几乎所有的App都有搜索功能 , 一般情况我们监听EditText控件,当值发生改变去请求搜索接口. 如:

<code class="hljs java has-numbering">etKey.addTextChangedListener(<span class="hljs-keyword">new</span> TextWatcher() {
    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">beforeTextChanged</span>(CharSequence s, <span class="hljs-keyword">int</span> start, <span class="hljs-keyword">int</span> count, <span class="hljs-keyword">int</span> after) {
    }

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onTextChanged</span>(CharSequence s, <span class="hljs-keyword">int</span> start, <span class="hljs-keyword">int</span> before, <span class="hljs-keyword">int</span> count) {
    }

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterTextChanged</span>(Editable s) {
        String key = etKey.getText().toString().trim();
        <span class="hljs-keyword">if</span> (key.length() > <span class="hljs-number">0</span>){
            search(key);<span class="hljs-comment">// 请求搜索接口,成功后把结果显示到界面上.</span>
        }
    }
});</code><ul class="pre-numbering" style="opacity: 0;"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li></ul>

这样做有两个问题:

  • 可能导致很多没有意义的请求,耗费用户流量(因为控件的值每更改一次立即就会去请求网络,而且只是最后输入的关键字是有用的)
  • 可能导致最终搜索的结果不是用户想要的. 例如,用户一开始输入关键字’AB’ 这个时候出现两个请求, 一个请求是A关键字, 一个请求是AB关键字. 表面上是’A’请求先发出去, ‘AB’请求后发出去. 如果后发出去的’AB’请求先返回, ‘A’请求后返回,那么’A’请求后的结果将会覆盖’AB’请求的结果. 从而导致搜索结果不正确.

2 如何解决问题

使用强大的RxJava的 debounce操作符 可以解决这个问题。

<code class="hljs avrasm has-numbering">subscription = RxTextView<span class="hljs-preprocessor">.textChanges</span>(etKey)
                <span class="hljs-preprocessor">.debounce</span>(<span class="hljs-number">400</span>, TimeUnit<span class="hljs-preprocessor">.MILLISECONDS</span>, AndroidSchedulers<span class="hljs-preprocessor">.mainThread</span>())
                <span class="hljs-preprocessor">.subscribeOn</span>(AndroidSchedulers<span class="hljs-preprocessor">.mainThread</span>())// 对etKey[EditText]的监听操作 需要在主线程操作
                //对用户输入的关键字进行过滤
                <span class="hljs-preprocessor">.filter</span>(new Func1<CharSequence, Boolean>() {
                    @Override
                    public Boolean <span class="hljs-keyword">call</span>(CharSequence charSequence) {
                        Log<span class="hljs-preprocessor">.d</span>(<span class="hljs-string">"RxJava"</span>, <span class="hljs-string">"filter is main thread : "</span> + (Looper<span class="hljs-preprocessor">.getMainLooper</span>() == Looper<span class="hljs-preprocessor">.myLooper</span>()))<span class="hljs-comment">;</span>
                        return charSequence<span class="hljs-preprocessor">.toString</span>()<span class="hljs-preprocessor">.trim</span>()<span class="hljs-preprocessor">.length</span>() > <span class="hljs-number">0</span><span class="hljs-comment">;</span>
                    }
                })
                <span class="hljs-preprocessor">.flatMap</span>(new Func1<CharSequence, Observable<List<String>>>() {
                    @Override
                    public Observable<List<String>> <span class="hljs-keyword">call</span>(CharSequence charSequence) {
                        Log<span class="hljs-preprocessor">.d</span>(<span class="hljs-string">"RxJava"</span>, getMainText(<span class="hljs-string">"flatMap"</span>))<span class="hljs-comment">;</span>
                        return searchApi<span class="hljs-preprocessor">.search</span>(charSequence<span class="hljs-preprocessor">.toString</span>())<span class="hljs-comment">;</span>
                    }
                })
                <span class="hljs-preprocessor">.subscribeOn</span>(Schedulers<span class="hljs-preprocessor">.io</span>())
                <span class="hljs-preprocessor">.observeOn</span>(AndroidSchedulers<span class="hljs-preprocessor">.mainThread</span>())
                <span class="hljs-preprocessor">.subscribe</span>(new Action1<List<String>>() {
                    @Override
                    public void <span class="hljs-keyword">call</span>(List<String> strings) {
                        tvContent<span class="hljs-preprocessor">.setText</span>(<span class="hljs-string">"search result:\n\n"</span>)<span class="hljs-comment">;</span>
                        tvContent<span class="hljs-preprocessor">.append</span>(strings<span class="hljs-preprocessor">.toString</span>())<span class="hljs-comment">;</span>
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void <span class="hljs-keyword">call</span>(Throwable throwable) {
                        throwable<span class="hljs-preprocessor">.printStackTrace</span>()<span class="hljs-comment">;</span>
                        tvContent<span class="hljs-preprocessor">.append</span>(<span class="hljs-string">"Error:"</span> + throwable<span class="hljs-preprocessor">.getMessage</span>())<span class="hljs-comment">;</span>
                    }
                })<span class="hljs-comment">;</span></code><ul class="pre-numbering" style="opacity: 0;"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li></ul>

上面代码的主要逻辑:

  • 使用debounce操作符设置: 只有当用户输入关键字后400毫秒才发射数据[说的直白点就是400毫秒后才会走后面的逻辑];
  • 使用filter操作符 对用户输入的关键字进行过滤:只有输入的关键字不为空,才会走后面的逻辑;
  • 使用flatMap操作符:使用最终的关键字去请求搜索接口

至此,避免EditText每改变一次就请求一次的情况。
但是,还有一个问题,上面说的导致搜索结果的错乱,上面的代码还是没有解决,比如停止输入400毫秒后, 那么肯定会开始请求Search接口, 但是用户又会输入新的关键字,这个时候上个请求还没有返回, 新的请求又去请求Search接口.这个时候有可能最后的一个请求返回, 第一个请求最后返回,导致最终显示的结果是第一次搜索的结果.

怎么去解决这个问题:可以使用switchMap操作符解决。

看看官网对 switchMap操作符 如何解释的:

<code class="hljs livecodeserver has-numbering">Returns <span class="hljs-operator">a</span> <span class="hljs-built_in">new</span> Observable <span class="hljs-keyword">by</span> applying <span class="hljs-operator">a</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">that</span> <span class="hljs-title">you</span> <span class="hljs-title">supply</span> <span class="hljs-title">to</span> <span class="hljs-title">each</span> <span class="hljs-title">item</span> <span class="hljs-title">emitted</span> <span class="hljs-title">by</span> <span class="hljs-title">the</span> <span class="hljs-title">source</span> <span class="hljs-title">Observable</span> <span class="hljs-title">that</span> <span class="hljs-title">returns</span> <span class="hljs-title">an</span> <span class="hljs-title">Observable</span>, </span>
<span class="hljs-operator">and</span> <span class="hljs-keyword">then</span> emitting <span class="hljs-operator">the</span> <span class="hljs-keyword">items</span> emitted <span class="hljs-keyword">by</span> <span class="hljs-operator">the</span> most recently emitted <span class="hljs-operator">of</span> these Observables.</code><ul class="pre-numbering" style="opacity: 0;"><li>1</li><li>2</li></ul>

switchMap操作符flatMap操作符 差不多,区别是switchMap操作符只会发射[emit]最近的Observables。

也就是说,当400毫秒后,发出第一个搜索请求,当这个请求的过程中,用户又去搜索了,发出第二个请求,不管怎样,switchMap操作符只会发射第二次请求的Observable。所以,在上面的代码基础上把flatMap改成switchMap就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值