0%

Kotlin协程与Room数据库异步操作suspend与Flow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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线程
轻量计算/内存操作 主线程