Long Polling vs WebSocket 在 Kotlin 中:实时应用该选什么?

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

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

开始课程

引言:为什么一个"简单的SELECT"会变成难题

\n\n

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

\n\n
val user = userDao.getUserById(42)\ntextView.text = user.name
\n\n

运行应用时,应用崩溃并抛出异常:

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

熟悉吗?这个在 Stack Overflow 上的问题已经获得了数千次浏览和数百条评论。这个错误吓到了很多初学者,但实际上这不是一个 bug —— 这是一种防错保护机制。Room(与其他合格的 ORM 一样)防止您意外阻塞用户界面。

\n\n

在本文中,我们将分析这为什么会发生,以及如何正确在 Android Room 配合 Kotlin 执行 SELECT 查询。您将学到三种经过验证的方法:LiveData、协程和 RxJava。让我们开始吧!

\n\n

核心问题:为什么 Room 会阻塞主线程?

\n\n

Android 默认是单线程系统。所有 UI 都在主线程上运行。如果您在主线程上开始读取数据库,界面将冻结直到查询完成。如果数据量大,这可能需要几秒钟——用户将看到一个"卡死"的应用。

\n\n

Room 采取了稳妥的做法:它禁止在主线程上执行任何长时间操作(读取、写入)。除非您在构建数据库时通过 .allowMainThreadQueries() 显式允许这样做。但您不应该这样做——这是一种反模式。

\n\n

以下是一个典型问题的样子:

\n\n
// DAO\n@Dao\ninterface UserDao {\n    @Query("SELECT * FROM users WHERE id = :userId")\n    suspend fun getUserById(userId: Int): User  // suspend — 已经是一个提示!\n}\n\n// Activity\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        // ❌ 错误!\n        val user = database.userDao().getUserById(42)\n    }\n}
\n\n

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

\n\n

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

\n\n

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

\n\n

步骤 1. 修改 DAO

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

步骤 2. 在 Activity/Fragment 中订阅

\n\n
class MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        database.userDao().getUserById(42).observe(this) { user ->\n            textView.text = user.name\n        }\n    }\n}
\n\n

优点: 代码最少,当 Activity 销毁时自动取消订阅,具有响应性——当表变化时数据自动更新。

\n\n

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

\n\n

方法 2:协程 — 现代的 Kotlin 方式

\n\n

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

\n\n

步骤 1. 带 suspend 的 DAO

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

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

\n\n
class MainVi                

博客

书籍推荐