我希望在利用 ROOM 库的应用程序中实现 SearchView 功能。在我的在线研究过程中,我注意到许多示例都遵循类似于以下的模式:
override fun onQueryTextChange(newText: String?): Boolean {
if (newText != null) {
mViewModel.searchAllDatabase(newText).observe(viewLifecycleOwner){
...
}
}
return true
}
现在,我很想知道我是否做对了整个事情。是否可以使用这种方法,每次用户输入新字母并触发 onQueryTextChange 方法时都会创建一个新的观察者?如果是这样的话,这是否可能导致内存泄漏?或者,另一方面,viewLifecycleOwner 是否自动处理这些观察者,确保它们在不再需要时被处置?
编辑:如果我们遵循这种方法会怎样:
override fun onQueryTextChange(newText: String?): Boolean {
if (newText != null) viewModel.searchQuery.value = newText
return true
}
onViewCreated 内部:
viewModel.allData.observe(viewLifecycleOwner) {
adapter.setData(it)
}
viewModel.searchQuery.observe(viewLifecycleOwner) { query ->
if (viewModel.allData.value != null) {
adapter.setData(viewModel.allData.value!!.filter { it.title.contains(query) })
}
}
从你所展示的情况来看,这确实泄漏了观察者。对于几乎每个输入或删除的字符,内存中都会有过时的观察者,直到片段的视图被破坏。更糟糕的是,如果这些观察者来自 Room,那么每次修改数据库时,它们都会运行查询,直到被释放为止。
避免这种情况的一种方法是使用基于搜索查询的切换映射操作。 switch map 操作正是我们想要的......当有新数据时,它取消观察之前的 LiveData 并开始新的 LiveData。这是在它自己的引擎盖下完成的,因此对于外部观察者来说,它只是一个 LiveData 流。
在 ViewModel 中,类似这样:
private val searchQuery = MutableLiveData("")
val queryText: String
get() = searchQuery.value
set(value) { searchQuery.value = value }
val searchedData = searchQuery.switchMap { query ->
if (query.isEmpty()) repo.allData else repo.searchAll(query)
}
现在,在您的 UI 类中,您仅在
viewModel.searchedData
中观察一次 onViewCreated()
,除了在 ViewModel.queryText
中设置 onQueryTextChange()
的新值之外什么也不做。
关于您提出的解决方案,有一些缺点。
首先,尝试直接读取 LiveData 的
value
而不是观察它确实很麻烦。在这种情况下,它保留了用户在 allData
获取第一个值集之前键入字母的可能性,使屏幕保持空白,直到用户再次键入。
其次,您手动筛选数据以自己执行搜索,这可能比让存储库执行搜索效率低。数据库针对此类事情进行了优化。