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就可以了。