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线程 轻量计算/内存操作 主线程
|