Android Room按主键查询单行数据报主线程访问数据库错误
Android Room 主键查询单条数据实现
实现目标
获取指定link对应的单条收藏数据,等价SQL:
SELECT * FROM favs WHERE link='link'
现有代码
Entity
@Entity(tableName = "favs") data class Favorite( @PrimaryKey @ColumnInfo(name = "link") val link : String, @ColumnInfo(name = "keywords") val keywords : String )
DAO
@Dao interface FavDAO { @Query("SELECT * FROM favs") fun getAllFavsLive(): Flow<List<Favorite>> @Query("SELECT * FROM favs WHERE link = :link") fun getOneFav(link: String): Favorite @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insert(link: Favorite) }
Repository
class FavRepo (private val favDao: FavDAO) { val allFavs: Flow<List<Favorite>> = favDao.getAllFavsLive() @WorkerThread suspend fun insert(link: Favorite) { favDao.insert(link) } fun getOneFav(link: String) = favDao.getOneFav(link) @WorkerThread suspend fun delete(link: String) { favDao.delete(link) } }
ViewModel
class FavViewModel (private val repository: FavRepo) : ViewModel() { val allFavs: LiveData<List<Favorite>> = repository.allFavs.asLiveData() fun insert(link: Favorite) = viewModelScope.launch { repository.insert(link) } fun getOneFav(link: String) = repository.getOneFav(link) } class FavViewModelFactory(private val repository: FavRepo) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(FavViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return FavViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
报错信息
运行时抛出异常:
java.lang.RuntimeException: Unable to start activity ComponentInfo{[package name removed].MainActivity}: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
触发原因:Room默认禁止在主线程执行数据库操作,避免长耗时任务阻塞UI线程。
已尝试无效方案
- 仅在DAO、Repository层的
getOneFav方法前加suspend修饰,未正确处理协程调用逻辑 - 将
getOneFav逻辑包裹在viewModelScope.launch块中,该写法返回Job类型,无法直接获取查询到的Favorite实体,错误代码:
fun getOneFav(link: String) = viewModelScope.launch { repository.getOneFav(link) }
- 基于MutableLiveData实现过滤查询,单条查询场景冗余度高,插入新数据后无法自动触发更新回调。
正确实现
根据业务场景选一种即可:
方案1:一次性查询(无需感知后续数据变更)
适用于仅单次查询数据、不需要关注对应数据后续增删改变化的场景,全链路使用suspend修饰,在协程中获取返回值:
- 修改DAO层方法,添加
suspend修饰,返回值设为可空类型,避免主键不存在时触发空指针:
@Query("SELECT * FROM favs WHERE link = :link") suspend fun getOneFav(link: String): Favorite?
- 修改Repository层方法,添加
suspend修饰:
suspend fun getOneFav(link: String): Favorite? = favDao.getOneFav(link)
- ViewModel层不要直接返回
Job,按需选择实现:
- 仅ViewModel内部使用数据:直接在
viewModelScope.launch块中调用获取结果 - 暴露给UI层调用:直接暴露挂起函数,由UI层使用生命周期关联的协程作用域调用
// 暴露挂起函数给UI层 suspend fun getOneFav(link: String): Favorite? = repository.getOneFav(link) // ViewModel内部处理逻辑示例 fun processFav(link: String) { viewModelScope.launch { val fav = repository.getOneFav(link) // 处理查询结果 } }
Activity/Fragment层调用示例:
lifecycleScope.launch { val fav = viewModel.getOneFav("目标link值") // 拿到结果后更新UI }
Room的suspend数据库方法会自动切换到后台线程执行,不会触发主线程访问异常。
方案2:可观察查询(自动感知数据变更)
适用于需要在对应收藏数据发生增删改时,自动接收最新数据推送的场景,直接返回Flow类型,Room自动处理线程切换,数据变化时自动推送最新值:
- 修改DAO层方法,返回
Flow类型:
@Query("SELECT * FROM favs WHERE link = :link") fun getOneFavLive(link: String): Flow<Favorite?>
- 修改Repository层方法:
fun getOneFavLive(link: String): Flow<Favorite?> = favDao.getOneFavLive(link)
- ViewModel层可转成LiveData暴露给UI层:
fun getOneFavLive(link: String): LiveData<Favorite?> = repository.getOneFavLive(link).asLiveData()
UI层直接观察该LiveData即可,数据变化时会自动触发回调,无需手动触发查询,也不会出现主线程访问异常。
内容的提问来源于stack exchange,提问作者Nilesh
相关产品推荐
相关产品推荐

