使用RxJava在Android中实现即时搜索

使用RxJava在Android中实现即时搜索


我正在开发一个新应用程序,该应用程序通常会与后端服务通信,以通过API接收数据。 在此示例中,我将开发一个搜索功能,其功能之一就是在输入文本时立即进行搜索。


即时搜寻


您认为没有什么复杂的。 您只需要将搜索组件放在页面上(最有可能在工具栏中),连接onTextChange事件onTextChange并执行搜索。 所以这就是我所做的:


 override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView // Set up the query listener that executes the search searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { Log.d(TAG, "onQueryTextSubmit: $query") return false } override fun onQueryTextChange(newText: String?): Boolean { Log.d(TAG, "onQueryTextChange: $newText") return false } }) return super.onCreateOptionsMenu(menu) } 

但这是问题所在。 由于我需要在输入时立即执行搜索,因此每当onQueryTextChange()事件处理程序onQueryTextChange() ,我都会转向API以获取第一组结果。 日志如下:


 D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TEST D/MainActivity: onQueryTextSubmit: TEST 

尽管事实上我只是在打印请求,但仍有五个API调用,每个API调用都会执行搜索。 例如,在云中,您需要为对API的每次调用付费。 因此,当我输入请求时,在发送请求之前需要一点延迟,以便最后只进行一次API调用。


现在,假设我想找到其他东西。 我删除测试并输入其他字符:


 D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: D/MainActivity: onQueryTextChange: S D/MainActivity: onQueryTextChange: SO D/MainActivity: onQueryTextChange: SOM D/MainActivity: onQueryTextChange: SOME D/MainActivity: onQueryTextChange: SOMET D/MainActivity: onQueryTextChange: SOMETH D/MainActivity: onQueryTextChange: SOMETHI D/MainActivity: onQueryTextChange: SOMETHIN D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING E D/MainActivity: onQueryTextChange: SOMETHING EL D/MainActivity: onQueryTextChange: SOMETHING ELS D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextSubmit: SOMETHING ELSE 

有20个API调用! 一个小的延迟将减少这些呼叫的数量。 我还想消除重复项,以便裁剪后的文本不会导致重复请求。 我可能还想过滤掉一些元素。 例如,您是否需要没有输入字符的搜索功能或只搜索一个字符的功能?


反应式编程


接下来有几种选择,但是现在我要集中讨论一种通常称为反应式编程和RxJava库的技术。 当我第一次遇到反应式编程时,我看到了以下描述:


ReactiveX是一种API,可与异步结构配合使用,并结合使用Observer和Iterator模式以及功能编程功能来操纵数据流或事件。

此定义不能完全说明ReactiveX的性质和优点。 如果它能解释的话,那么仅适用于已经熟悉该框架原理的人员。 我还看到了这样的图表:


延迟算子图


该图说明了操作员的角色,但并未完全理解其本质。 因此,让我们看看是否可以通过一个简单的示例来更清楚地解释该图。


让我们先准备我们的项目。 您将在应用程序的build.gradle文件中需要一个新的库:


 implementation "io.reactivex.rxjava2:rxjava:2.1.14" 

记住要同步项目依赖项以加载库。


现在让我们看一个新的解决方案。 使用旧方法,我在输入每个新字符时访问了API。 使用新方法,我将创建一个Observable


 override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView // Set up the query listener that executes the search Observable.create(ObservableOnSubscribe<String> { subscriber -> searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String?): Boolean { subscriber.onNext(newText!!) return false } override fun onQueryTextSubmit(query: String?): Boolean { subscriber.onNext(query!!) return false } }) }) .subscribe { text -> Log.d(TAG, "subscriber: $text") } return super.onCreateOptionsMenu(menu) } 

此代码与旧代码完全相同。 日志如下:


 D/MainActivity: subscriber: T D/MainActivity: subscriber: TE D/MainActivity: subscriber: TES D/MainActivity: subscriber: TEST D/MainActivity: subscriber: TEST 

但是,使用新技术之间的主要区别在于存在反应性流Observable 。 文本处理程序(在这种情况下为请求处理程序)使用onNext()方法将元素发送到流中。 Observable拥有处理这些元素的订户。


我们可以在订阅前创建方法链,以减少要处理的字符串列表。 首先,发送的文本将始终为小写,并且该行的开头和结尾没有空格:


 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .subscribe { text -> Log.d(TAG, "subscriber: $text" } 

我削减了方法以显示最重要的部分。 现在相同的日志如下:


 D/MainActivity: subscriber: t D/MainActivity: subscriber: te D/MainActivity: subscriber: tes D/MainActivity: subscriber: test D/MainActivity: subscriber: test 

现在让我们应用250ms的延迟,期待更多内容:


 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(250, TimeUnit.MILLISECONDS) .subscribe { text -> Log.d(TAG, "subscriber: $text" } 

最后,删除重复的流,以便仅处理第一个唯一请求。 随后的相同请求将被忽略:


 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .subscribe { text -> Log.d(TAG, "subscriber: $text" } 

注意事项 佩雷夫 在这种情况下,使用distinctUntilChanged()运算符更为合理,因为否则,在对任何字符串重复搜索的情况下,查询将被简单地忽略。 在执行这种搜索时,仅关注上一个成功的请求,而忽略与上一个相同的新请求是合理的。

最后,我们过滤掉空查询:


 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .filter { text -> text.isNotBlank() } .subscribe { text -> Log.d(TAG, "subscriber: $text" } 

此时,您会注意到日志中仅显示一个(或可能两个)消息,这表明减少了API调用。 在这种情况下,该应用程序将继续正常运行。 此外,在某些情况下,如果您输入某些内容,然后又删除并再次输入,也会减少对API的调用。


您可以根据自己的目标将更多不同的运算符添加到该管道中。 我相信它们对于处理与API交互的输入字段非常有用。 完整的代码如下:


 // Set up the query listener that executes the search Observable.create(ObservableOnSubscribe<String> { subscriber -> searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String?): Boolean { subscriber.onNext(newText!!) return false } override fun onQueryTextSubmit(query: String?): Boolean { subscriber.onNext(query!!) return false } }) }) .map { text -> text.toLowerCase().trim() } .debounce(250, TimeUnit.MILLISECONDS) .distinct() .filter { text -> text.isNotBlank() } .subscribe { text -> Log.d(TAG, "subscriber: $text") } 

现在,我可以将日志消息替换为对ViewModel的调用以启动API调用。 但是,这是另一篇文章的主题。


结论


使用此简单的技术将文本元素包装在Observable并使用RxJava,您可以减少执行服务器操作所需的API调用次数,并提高应用程序的响应速度。 在本文中,我们仅涵盖了RxJava的一小部分,因此,我为您提供了有关此主题的更多阅读的链接:


Source: https://habr.com/ru/post/zh-CN430394/


All Articles