为什么 Room 会抱怨主线程?解析错误及 3 种解决方法

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么“简单的 SELECT”会变成头疼的问题



您正在使用 Room 和 Kotlin 编写您的第一个 Android 应用程序。一切进展顺利,直到需要从数据库中读取数据。您编写了类似这样的代码:



val user = userDao.getUserById(42)textView.text = user.name


运行后——应用程序崩溃并抛出异常:



java.lang.IllegalStateException: Cannot access database on the main threadsince it may potentially lock the UI for a long period of time.


熟悉吗?这个问题在 Stack Overflow 上获得了数千次浏览和数百条评论。这个错误吓坏了新手,但实际上这不是一个 bug——这是一种防止误操作的保护机制。Room(像任何优秀的 ORM 一样)不允许您意外地阻塞用户界面。



在本文中,我们将分析为什么会发生这种情况,以及如何正确在 Android Room 上使用 Kotlin 执行 SELECT 查询。您将了解到三种经过验证的方法:LiveData、Coroutines 和 RxJava。开始吧!



要点简述:为什么 Room 会阻塞主线程?



Android 默认是单线程系统。所有 UI 都在 main thread(主线程)上运行。如果您开始在这个线程上读取数据库,界面将会冻结,直到查询完成。对于大量数据,这可能需要几秒钟——用户会看到一个“挂起”的应用程序。



Room 采取了预防措施:它禁止在主线程上执行任何耗时操作(读取、写入)。除非您通过 .allowMainThreadQueries() 在构建数据库时明确允许这样做。但最好不要这样做——这是一种反模式。



典型问题如下所示:



// DAO@Daointerface UserDao {    @Query("SELECT * FROM users WHERE id = :userId")    suspend fun getUserById(userId: Int): User  // suspend — 已经是个提示!}

// Activityclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ❌ 错误! val user = database.userDao().getUserById(42) }}


即使您将函数设为 suspend,但如果没有在协程中调用它——Room 仍然会抛出异常。我们来修复它!



方法 1:LiveData — 简单且响应式



最简单的方法是从 DAO 返回 LiveData。Room 自行管理线程:查询在后台执行,结果返回到 UI。



步骤 1. 修改 DAO



@Daointerface UserDao {    @Query("SELECT * FROM users WHERE id = :userId")    fun getUserById(userId: Int): LiveData<User>}


步骤 2. 在 Activity/Fragment 中订阅



class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)

database.userDao().getUserById(42).observe(this) { user -> textView.text = user.name } }}


优点:代码量最少,Activity 销毁时自动取消订阅,响应式——当表数据更改时自动更新。



缺点:LiveData 与生命周期绑定,不易与其他数据源(网络、SharedPreferences)组合。



方法 2:Coroutines — 现代的 Kotlin 方式



如果您使用 Kotlin Coroutines(您应该使用),这是最优雅的方法。函数标记为 suspend,调用包装在具有正确 Dispatchers.IO 的协程中。



步骤 1. 使用 suspend 的 DAO



@Daointerface UserDao {    @Query("SELECT * FROM users WHERE id = :userId")    suspend fun getUserById(userId: Int): User}


步骤 2. 从 ViewModel 调用(推荐)



class MainVi                

博客

书籍推荐