将RecyclerView数据获取改为协程的相关问题及Cursor处理疑问
问题解答
一、为什么协程中调用LiveData.setValue()会抛出异常?
你在viewModelScope.launch(Dispatchers.IO)里调用setValue()报错,核心原因是LiveData的setValue()方法强制要求在主线程执行,而你显式指定了Dispatchers.IO,协程运行在IO后台线程,违反了这个规则,所以抛出IllegalStateException。
viewModelScope的默认调度器是Dispatchers.Main,如果不指定Dispatchers.IO,协程会在主线程运行,此时调用setValue()是合法的。postValue()的作用是把LiveData的更新任务提交到主线程的消息队列,由主线程执行实际的setValue()操作,因此不会触发线程检查。
二、Cursor的关闭警告该如何处理?
绝对不能在调用postValue()后直接调用cursor.close()——因为此时RecyclerView还需要使用这个Cursor加载数据,提前关闭会导致后续操作抛出CursorWindowAllocationException之类的错误。
正确的处理方式:
- 在LiveData的观察者中管理Cursor生命周期:当观察者收到新Cursor时,关闭之前的旧Cursor。示例如下:
private var oldCursor: Cursor? = null dbCursor.observe(viewLifecycleOwner) { newCursor -> oldCursor?.close() oldCursor = newCursor adapter.swapCursor(newCursor) } - 避免使用
!!强制非空:contentResolver.query()可能返回null(比如权限不足、URI错误),直接用!!会引发空指针异常,应该保留null并在后续处理:val cursor = getApplication<Application>().contentResolver.query(...) dbCursor.postValue(cursor) - 页面生命周期结束时清理:在Fragment/Activity的
onDestroy()中,获取LiveData当前持有的Cursor并关闭:override fun onDestroy() { super.onDestroy() dbCursor.value?.close() oldCursor?.close() }
完整代码(修正后)
private fun loadTasks() { val func = "loadTasks" Log.d(TAG, func) val projection = arrayOf( TasksContract.Columns.ID, TasksContract.Columns.TASK_NAME, TasksContract.Columns.TASK_DESCRIPTION, TasksContract.Columns.TASK_SORT_ORDER ) val sortOrder = "${TasksContract.Columns.TASK_SORT_ORDER}, ${TasksContract.Columns.TASK_NAME}" viewModelScope.launch(Dispatchers.IO) { val cursor = getApplication<Application>().contentResolver.query( TasksContract.CONTENT_URI, projection, null, null, sortOrder ) dbCursor.postValue(cursor) // 移除!!,允许null } Log.d(TAG, "$func done") }
内容的提问来源于stack exchange,提问作者Ben Edwards
相关产品推荐
相关产品推荐

