Kotlin协程与Room数据库异步操作suspend与Flow
val list = database.userDao().getAllUsers()// Room suspend 查询,会在IO线程执行。suspend 表示挂起函数,它本身不会创建新线程,但可以挂起当前协程,让协程切换到其他线程去执行耗时操作,然后恢复。database.userDao().getAllUsers()// 后台线程。.catch { e -> Log.e("DB", "查询失
·
Kotlin 协程与 Room 数据库异步操作:suspend 与 Flow 全解析
在 Android 开发中,Room 数据库操作不能在主线程执行耗时操作,否则会抛出 android.os.NetworkOnMainThreadException 或 ANR。Kotlin 协程提供了非常方便的方式来处理异步操作,suspend 与 Flow 是其中最常用的两种模式。
1. suspend:异步函数(一次性操作)
suspend 表示挂起函数,它本身不会创建新线程,但可以挂起当前协程,让协程切换到其他线程去执行耗时操作,然后恢复。
用法示例
@Dao
interface GasProjectDao {
@Insert
suspend fun insertGasProject(gasProject: GasProject)
@Query("SELECT * FROM GasProject WHERE id = :id")
suspend fun getGasProjectById(id: Int): GasProject?
}
调用方式
lifecycleScope.launch {
val project = GasProject(name = "测试项目")
gasProjectDao.insertGasProject(project) // 挂起函数在协程中调用
val savedProject = gasProjectDao.getGasProjectById(project.id)
textView.text = "项目名称: ${savedProject?.name}"
}
✅ 特点:
执行一次性操作(插入、查询单条数据)
挂起函数必须在协程中调用
可以和 Dispatchers.IO 配合,实现真正的后台线程执行
2. Flow:异步版可观察列表(持续更新)
Flow 是 Kotlin 协程提供的响应式流,用于表示数据的异步流,类似 RxJava 的 Observable。Room 与 Flow 结合,可以实现数据库表数据变化自动推送到 UI。
用法示例
@Dao
interface GasProjectDao {
@Query("SELECT * FROM GasProject")
fun getAllGasProject(): Flow<List<GasProject>>
}
调用方式
lifecycleScope.launch {
gasProjectDao.getAllGasProject()
.collect { projectList ->
// 每次数据库数据变化,这里都会收到最新列表
recyclerViewAdapter.submitList(projectList)
}
}
✅ 特点:
可观察数据库表的变化(自动推送 UI)
持续订阅,多次更新
可以和协程调度器一起使用,比如切换到 Dispatchers.Main 更新 UI
3. suspend 与 Flow 的区别
特性 suspend Flow
调用频率 一次操作 持续订阅,数据变化多次
线程 可挂起在协程中切换线程 也可挂起,支持调度线程切换
用途 插入、单条查询、一次性更新 列表查询、UI 自动刷新、响应式数据
返回值 具体类型 Flow<T> 流对象,需要 collect
示例 suspend fun insert(...) fun getAll(): Flow<List<...>>
4. 使用技巧与注意事项
主线程安全
suspend 或 Flow 都不能阻塞主线程
UI 更新需要 withContext(Dispatchers.Main) 或 collect 在主线程
一次性 vs 持续更新
插入、更新单条记录 → suspend
观察数据库表变化 → Flow
与 LiveData 结合
Room 支持直接返回 Flow 或 LiveData,可以配合 lifecycleScope 或 viewLifecycleOwner.lifecycleScope
gasProjectDao.getAllGasProject()
.asLiveData()
.observe(this) { list ->
adapter.submitList(list)
}
异常处理
协程可以使用 try/catch 捕获异常,也可以在 Flow 中使用 catch
lifecycleScope.launch {
gasProjectDao.getAllGasProject()
.catch { e -> Log.e("DB", "查询失败", e) }
.collect { list -> adapter.submitList(list) }
}
5. 总结
suspend = 一次异步操作,适合插入、更新、单条查询
Flow = 可观察数据流,适合列表查询、UI 自动刷新
配合 lifecycleScope 可以轻松实现协程 + Room 的异步、响应式编程
💡 技巧:如果你想同时处理一次性操作和持续观察数据,可以 suspend + Flow 结合使用,保持代码简洁、响应快速。
科普:协程使用情况
1️⃣ 默认情况:lifecycleScope.launch { ... }
默认线程:主线程(Dispatchers.Main)。
适合做的操作:
更新 UI(修改 View、TextView、RecyclerView 等)
处理用户交互事件(点击、滑动等)
调用不耗时的本地操作(小量内存计算、轻量对象操作)
示例:
lifecycleScope.launch {
textView.text = "Hello World" // UI操作,主线程
}
2️⃣ 切换到 IO 线程:lifecycleScope.launch(Dispatchers.IO) { ... }
适合操作:
数据库读写(Room 的非 suspend 方法或者耗时操作)
文件操作(读写本地文件)
网络请求(HTTP、Socket)
任何耗时、可能阻塞线程的操作
示例:
lifecycleScope.launch(Dispatchers.IO) {
val list = database.userDao().getAllUsers() // Room suspend 查询,会在IO线程执行
withContext(Dispatchers.Main) {
recyclerView.adapter = UserAdapter(list) // 更新UI要切回主线程
}
}
3️⃣ 混合使用
可以在 同一个协程里 通过 withContext 切换线程:
lifecycleScope.launch {
val list = withContext(Dispatchers.IO) {
database.userDao().getAllUsers() // 后台线程
}
recyclerView.adapter = UserAdapter(list) // 主线程
}
✅ 总结:
UI操作 主线程
Room suspend/数据库操作 IO线程
网络请求 IO线程
轻量计算/内存操作 主线程更多推荐


所有评论(0)