SearchView列表过滤场景下,如何在新查询触发时取消正在运行的协程?
解决快速搜索时协程重复执行的问题
嘿,这个场景太常见了——用户快速敲键盘时,旧的搜索协程还在慢悠悠过滤数据,新的查询又触发了新协程,最后可能导致UI显示的结果和当前输入不匹配,还浪费资源。先说说你尝试的代码问题在哪:你每次调用onQuery都新建了一个job,然后立刻取消它再启动,这根本没触碰到之前正在运行的协程,因为这个新job和旧协程完全没关系呀!
正确的实现方式:跟踪并取消旧协程
我们只需要在ViewModel里维护一个变量,用来保存当前正在运行的搜索协程,每次新查询来的时候,先取消旧的协程,再启动新的。
完整代码示例
import kotlinx.coroutines.Job import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext // 在你的ViewModel类中 private var searchJob: Job? = null // 可选:记录上次查询内容,避免重复处理相同查询 private var lastSearchQuery: String = "" fun onQuery(query: String) { // 优化:如果查询内容和上次完全一样,直接返回,不用重复处理 if (query == lastSearchQuery) return lastSearchQuery = query // 取消之前正在运行的搜索协程(如果有的话) searchJob?.cancel() // 启动新的搜索协程,并把引用保存到searchJob searchJob = viewModelScope.launch(Dispatchers.Default) { // 协程被取消后,直接终止后续操作,避免无用计算 if (!isActive) return@launch // 执行过滤逻辑:这里可以根据需求加忽略大小写等优化 val personsFound = persons.filter { person -> person.nom.contains(query, ignoreCase = true) || person.prenom.contains(query, ignoreCase = true) } // 再次检查协程状态,避免取消后还去更新UI if (!isActive) return@launch // 切换到主线程更新UI withContext(Dispatchers.Main) { _items.value = personsFound } } }
关键细节解释
searchJob变量:专门用来跟踪当前正在运行的搜索协程,每次新查询触发时,先调用cancel()终止旧协程,确保只有最新的协程能完成过滤并更新UI。isActive检查:协程被取消后,isActive会变为false,提前检查可以避免执行后续的无用代码(比如过滤完成后再切换主线程,这时候协程已经取消了,withContext也会失败,但提前返回更高效)。- 忽略大小写优化:添加
ignoreCase = true让搜索更符合用户预期,毕竟用户不会在意名字的大小写。 - 重复查询过滤:记录上次查询内容,避免相同的查询重复触发协程,进一步提升性能。
这样修改后,不管用户敲键盘有多快,始终只有最新的查询协程在运行,旧的会被立刻取消,UI也会始终显示和当前输入匹配的结果。
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

