Room + Flow 完整教程(现代 Android 官方方案)
现代 Android 开发中:
Room + Flow + Compose/ViewModel
已经是官方推荐数据库架构。
真正强大的地方是:
数据变化 → UI 自动刷新
不需要:
notifyDataSetChanged()- 手动刷新
- 回调通知
这一篇会讲:
- Room 是什么
- Flow 为什么适合数据库
- Room + Flow 工作原理
- MVVM 实战
- Compose 配合
- 自动刷新机制
- 企业级最佳实践
一、Room 是什么?
Room 是 Android 官方数据库 ORM。
底层:SQLite
但 Room 帮你:
- 自动建表
- 自动 SQL 映射
- 自动线程检查
- 自动 Flow 更新
二、为什么 Room 要配合 Flow?
传统数据库:
vallist=dao.getUsers()问题:数据库变了,UI 不会自动更新
你得:手动刷新、LiveData、回调。很麻烦。
三、Flow 的核心优势
使用Flow<List<User>>后:
- 数据库变化
- Flow 自动重新发送数据
- UI 自动刷新
这就是:响应式数据库
四、添加依赖
// Roomimplementation"androidx.room:room-runtime:2.6.1"ksp"androidx.room:room-compiler:2.6.1"// Kotlin 扩展implementation"androidx.room:room-ktx:2.6.1"// Flow 协程implementation"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"五、Entity(数据表)
@Entity(tableName="user")dataclassUser(@PrimaryKeyvalid:Int,valname:String,valage:Int)六、DAO(最核心)
DAO = Data Access Object,数据库操作入口。
七、最重要的 Flow 查询
@DaointerfaceUserDao{@Query("SELECT * FROM user")fungetUsers():Flow<List<User>>}重点:返回Flow
八、为什么不用 suspend?
| suspend | Flow |
|---|---|
suspend fun getUsers() | Flow<List<User>> |
| 只返回一次 | 持续监听数据库变化 |
九、Room 自动更新原理(重点)
这是 Room 最强大的地方。
Room 内部维护:InvalidationTracker
数据库变化 ↓ 通知 Flow ↓ Flow:重新 emit 最新数据 ↓ UI:自动刷新十、完整流程图(非常重要)
数据库 insert/update/delete ↓ Room InvalidationTracker ↓ Flow emit 新数据 ↓ collectAsState 收到 ↓ Compose 重组 ↓ UI自动刷新这就是:现代 Android 响应式数据库架构
十一、插入数据
@Insertsuspendfuninsert(user:User)十二、删除数据
@Deletesuspendfundelete(user:User)十三、更新数据
@Updatesuspendfunupdate(user:User)插入数据、删除、更新,为什么用suspend,不用Flow
Flow 本质是持续的数据流(Stream)
它适合:
未来还会不断变化的数据例如:
数据库变化 Socket消息 搜索输入 传感器数据 聊天消息插入、删除、更新操作并没有“流”
看:
@Insertsuspendfun insert(user: User)插入:
执行一次 结束它没有:
后续数据所以:Flow 没意义
如果 insert 用 Flow 会怎样?
理论上你可以:
fun insert(user: User): Flow<Unit>但:
完全没必要因为:
它只会:
emit 一次 结束这其实退化成:suspend
面试高频回答(建议背下来)
为什么 Room 查询用 Flow,而插入删除更新用 suspend?
标准答案:
因为查询是持续变化的数据源,需要响应式监听数据库变化,所以适合使用 Flow。 而插入、删除、更新属于一次性操作,只需要执行并返回一次结果,因此使用suspend更合适。十四、Database
@Database(entities=[User::class],version=1)abstractclassAppDatabase:RoomDatabase(){abstractfunuserDao():UserDao}十五、创建数据库
valdb=Room.databaseBuilder(context,AppDatabase::class.java,"app.db").build()十六、Repository(标准架构)
classUserRepository(privatevaldao:UserDao){fungetUsers()=dao.getUsers()suspendfuninsert(user:User){dao.insert(user)}}十七、为什么需要 Repository?
因为:ViewModel 不应该直接操作数据库,否则:
- 强耦合
- 不好测试
- 架构混乱
十八、ViewModel + StateFlow
现代标准方案。
classUserViewModel(privatevalrepository:UserRepository):ViewModel(){valusers=repository.getUsers().stateIn(scope=viewModelScope,started=SharingStarted.WhileSubscribed(5000),initialValue=emptyList())funaddUser(){viewModelScope.launch{repository.insert(User(1,"Tom",18))}}}十九、stateIn 是什么?
作用:Flow → StateFlow
因为:Compose 更适合 StateFlow。
二十、Compose 收集数据库
@ComposablefunUserPage(vm:UserViewModel){valusersbyvm.users.collectAsState()LazyColumn{items(users){Text(it.name)}}}二十一、真正的神奇之处
调用insert()后,UI 自动刷新,完全不用notifyDataSetChanged()。
二十二、为什么能自动刷新?
Room ↓ Flow ↓ collectAsState ↓ Recomposition形成:响应式链路
二十三、collectAsStateWithLifecycle(推荐)
官方更推荐collectAsStateWithLifecycle()
依赖
implementation"androidx.lifecycle:lifecycle-runtime-compose"使用
valusersbyvm.users.collectAsStateWithLifecycle()为什么推荐它?自动处理生命周期,页面不可见自动停止收集。
二十四、Room 查询线程问题
suspend 查询
@Query("SELECT * FROM user")suspendfungetUsers():List<User>Room 自动后台线程执行。
Flow 查询
Flow<List<User>>也自动后台线程。
所以:不需要withContext(IO),这是 Room 帮你做好的。
二十五、Flow 为什么特别适合数据库?
因为数据库本质就是:持续变化的数据源
Flow 天生适合:观察变化
二十六、多个表联合查询
dataclassUserWithArticles(@Embeddedvaluser:User,@Relation(parentColumn="id",entityColumn="userId")valarticles:List<Article>)二十七、事务查询
@Transaction@Query("SELECT * FROM user")fungetUserWithArticles():Flow<List<UserWithArticles>>二十八、Flow 防抖搜索(经典)
DAO
@Query("SELECT * FROM user WHERE name LIKE '%' || :keyword || '%'")funsearch(keyword:String):Flow<List<User>>ViewModel
valkeyword=MutableStateFlow("")valusers=keyword.debounce(300).flatMapLatest{dao.search(it)}二十九、flatMapLatest 为什么重要?
因为输入变化时:
- 取消旧查询
- 启动新查询
避免:搜索请求堆积
三十、Room + Paging3
现代大列表方案。
DAO
@Query("SELECT * FROM user")funpagingSource():PagingSource<Int,User>Pager
Pager(config=PagingConfig(20)){dao.pagingSource()}.flowCompose 分页
valitems=vm.users.collectAsLazyPagingItems()三十一、Room + Flow + Compose 真正完整链路
这是现代 Android 最核心架构。
执行流程:
Room(SQLite) ↓ Flow ↓ Repository ↓ ViewModel ↓ StateFlow ↓ Compose collectAsState ↓ Recomposition ↓ UI刷新三十二、Room 常见错误
| 错误 | 说明 |
|---|---|
| 在主线程操作数据库 | 禁止allowMainThreadQueries() |
| UI 直接操作 DAO | 应该:Compose → ViewModel → Repository → DAO |
| Flow collect 泄漏 | flow.collect没有生命周期 |
正确:collectAsStateWithLifecycle()
三十三、Room vs LiveData
现在Flow已经基本替代LiveData,因为:
| Flow | LiveData |
|---|---|
| 更强 | 受限 |
| 支持操作符 | 功能单一 |
| 协程统一 | 非协程 |
| Kotlin 原生 | Android 特定 |
三十四、Room + Flow 面试题
1. Room 为什么支持自动刷新?
因为:InvalidationTracker
2. Flow 和 suspend 查询区别?
| suspend | Flow |
|---|---|
| 一次返回 | 持续监听 |
| 单次数据 | 数据流 |
3. Room 查询为什么不卡主线程?
Room 自动线程调度。
4. 为什么推荐 StateFlow?
因为:Compose 状态驱动
5. Flow 为什么适合数据库?
因为数据库是持续变化的数据源
三十五、企业级最佳实践(非常重要)
标准架构:
Compose UI ↓ ViewModel ↓ StateFlow ↓ Repository ↓ Room DAO ↓ SQLite三十六、真正理解 Room + Flow
以前 Android:
数据库变化 ↓ 手动通知UI ↓ RecyclerView刷新现代 Android:
数据库变化 ↓ Flow自动发射 ↓ Compose自动重组 ↓ UI自动刷新三十七、真正的大脑模型(最重要)
看到Flow<List<User>>,脑子里自动出现:
数据库监听器 ↓ 数据变化自动emit ↓ UI自动刷新看到collectAsState(),自动想到:
Flow → State → Compose重组看到stateIn(viewModelScope),自动想到:
冷Flow → 热StateFlow → UI状态共享三十八、最后一句(现代 Android 的本质)
现在 Android 官方整个方向:
已经从:命令式UI
变成:响应式状态驱动
核心链路:
数据库 ↓ Flow ↓ StateFlow ↓ Compose ↓ 自动UI刷新这就是现代 Android 架构真正的核心。