📱 这是一份为 Android 开发团队精心准备的 Kotlin Flow 完全指南
目标:让每个开发者都能从"听说过"进阶到"能用好"
🎯 为什么我们要学 Kotlin Flow?
现状痛点
在学 Flow 之前,你可能遇到过这些问题:
| 问题 | 表现 | Flow 如何解决 |
|---|---|---|
| 回调地狱 | 多层网络请求嵌套混乱 | 链式调用,代码线性可读 |
| 内存泄漏 | 后台任务没取消导致 OOM | 自动生命周期管理 |
| 异常处理难 | 异常被吞掉或崩溃 | 统一的异常处理机制 |
| 线程切换复杂 | 手动管理 Handler、Thread | Dispatcher 自动切换 |
| 状态同步困难 | 数据更新 UI 不及时 | 响应式自动同步 |
Flow 的核心价值
- ✅ 简化异步代码
- ✅ 自动生命周期管理
- ✅ 完整的异常处理
- ✅ 实时数据响应(代替 LiveData)
- ✅ 与 Coroutine 完美结合
💡 核心概念速览(10 分钟理解)
概念 1:Flow 是什么?
简单说:Flow 是一条"数据传送带"

现实例子:快递物流
- 📦 数据源 = 发货仓库
- 🏭 中间加工 = 流转中心(分拣、扫描)
- 📍 最终消费者 = 你的家门口
概念 2:Cold Flow vs Hot Flow(重要!)
❄️ Cold Flow(冷流)= 懒人快递
scss
// 使用 flow { } 创建的 Flow 是冷流
val coldFlow = flow {
println("开始加工快递") // 有人下单才执行
emit(1)
}
// 第一个订阅者
coldFlow.collect { println("用户1收到: $it") }
// 输出: 开始加工快递 → 用户1收到: 1
// 第二个订阅者
coldFlow.collect { println("用户2收到: $it") }
// 输出: 开始加工快递 → 用户2收到: 1
// ⚠️ 执行了两次!每个订阅都是独立的
特点:
- ✅ 没人订阅就不执行(节省资源)
- ❌ 重复订阅会重复执行
- 🎯 适用于:网络请求、数据库查询
🔥 Hot Flow(热流)= 直播广播
scss
// 使用 MutableSharedFlow/StateFlow 创建的是热流
val hotFlow = MutableSharedFlow<Int>()
// 发送者不等订阅者,直接广播
launch {
hotFlow.emit(1)
hotFlow.emit(2)
hotFlow.emit(3)
}
// 晚到的订阅者错过前面的值
delay(100)
hotFlow.collect { println("迟到的用户: $it") }
// 输出: 迟到的用户: 2, 迟到的用户: 3
// ❌ 错过了第一个值 1
特点:
- ✅ 无论有没有订阅都执行
- ✅ 所有订阅者共享同一条流
- ❌ 资源消耗更多
- 🎯 适用于:事件通知、用户交互
快速判断:
css
需要维护当前状态? → StateFlow(热流)
只需要发送事件通知? → SharedFlow(热流)
想要每次都重新执行? → Flow(冷流)
想要实时响应数据库变化? → Room Flow(冷流)
🛠️ 实战:最常见的 5 个场景
场景 1:单个网络请求(最基础)
业务需求:点击按钮加载用户信息
kotlin
// ViewModel 中
class UserViewModel : ViewModel() {
// 将网络请求包装成 Flow
fun fetchUser(userId: String) = flow {
try {
val user = apiService.getUser(userId)
emit(user) // 发送数据
} catch (e: Exception) {
throw e // 异常会传给 UI 层处理
}
}
}
// Activity/Fragment 中
viewModel.fetchUser("123")
.catch { exception ->
Toast.show("加载失败: ${exception.message}")
}
.collect { user ->
// 拿到数据,更新 UI
updateUserUI(user)
}
优点:
- ✅ 代码简洁,易读易维护
- ✅ 统一的异常处理
- ✅ 自动线程切换
场景 2:顺序网络请求(依赖关系)
业务需求:先获取用户信息,再根据用户 ID 获取他的文章列表
用户输入 ID → 查询用户信息 → 获取该用户的文章 → 展示文章列表
kotlin
// 错误方式 ❌(回调地狱)
apiService.getUser("123") { user ->
apiService.getPosts(user.id) { posts ->
updateUI(user, posts)
}
}
// 正确方式 ✅(Flow 方式)
fun getUserWithPosts(userId: String) = flow {
val user = apiService.getUser(userId)
val posts = apiService.getPosts(user.id)
emit(UserWithPosts(user, posts))
}
// 使用
viewModel.getUserWithPosts("123")
.catch { Toast.show("加载失败") }
.collect { data ->
updateUI(data.user, data.posts)
}
为什么用 Flow?
- 📊 从"嵌套金字塔"变成"线性流水线"
- 🎯 依赖关系一目了然
- 🛡️ 异常处理统一
场景 3:并行网络请求(独立关系)
业务需求:首页需要同时加载用户、推荐、广告等数据(它们互不依赖)
ini
┌─ 获取用户信息 ─┐
│ ├─ 合并 → 展示首页
├─ 获取推荐文章 ─┤
│ │
└─ 获取广告数据 ─┘
时间对比:
顺序执行: 1s + 1s + 1s = 3s ❌
并行执行: max(1s, 1s, 1s) = 1s ✅
scss
// 使用 combine 操作符(并行加载)
fun getHomeData(): Flow<HomeData> = combine(
apiService.getUser(), // 流1
apiService.getRecommends(), // 流2
apiService.getAds() // 流3
) { user, recommends, ads ->
// 所有数据都到达了,才执行这里
HomeData(user, recommends, ads)
}
// 使用
viewModel.getHomeData()
.catch { Toast.show("加载失败") }
.collect { homeData ->
updateHomeUI(homeData)
}
性能对比:
- 🐢 顺序执行:需要 3 秒
- 🚀 并行执行:只需 1 秒(快 3 倍!)
场景 4:实时搜索(防抖 + 去重 + 实时请求)
业务需求:搜索框边打字边搜索(但要避免频繁请求服务器)
makefile
用户输入: k → ko → kot → kotl → kotlin
↓ ↓ ↓ ↓ ↓
请求? 请求? 请求? 请求? 最后请求
问题分析:
- ❌ 用户每输入一个字就请求?太浪费流量
- ❌ 新的搜索来了,还在处理旧的搜索结果?太慢
解决方案:
kotlin
fun search(queryFlow: Flow<String>): Flow<List<SearchResult>> = queryFlow
.debounce(300) // 延迟 300ms(停止输入 300ms 后才搜索)
.filter { it.isNotEmpty() } // 过滤空字符串
.distinctUntilChanged() // 去重(相同词不重复搜索)
.flatMapLatest { query -> // 关键!新搜索来了,取消旧搜索
apiService.search(query)
}
.catch { emptyFlow() } // 搜索出错显示空列表
实际效果:
makefile
用户输入: k k o t l i n (间隔 100ms)
↓
debounce ✗ ✗ ✗ ✗ ✗ ✗ → 触发搜索(用户停止后)
distinctUntilChanged → "kotlin" (去重)
flatMapLatest → 仅处理最新的搜索
结果: 只发出 1 个网络请求!
场景 5:数据库 + 实时监听
业务需求:实时显示本地数据库的内容,数据库一改马上更新 UI
kotlin
// Room DAO 层
@Dao
interface TodoDao {
@Query("SELECT * FROM todos ORDER BY id DESC")
fun getAllTodos(): Flow<List<Todo>> // ← 返回 Flow!
suspend fun insertTodo(todo: Todo)
}
// ViewModel
class TodoViewModel(private val dao: TodoDao) : ViewModel() {
val todos: Flow<List<Todo>> = dao.getAllTodos()
.catch { exception ->
Log.e("TodoVM", "加载失败", exception)
emitAll(emptyFlow())
}
}
// Activity
viewModel.todos.collect { todoList ->
// 每当数据库改变,这里自动执行
updateTodoList(todoList)
}
优点:
- ✅ 无需手动查询数据库
- ✅ 数据库一改,UI 自动更新
- ✅ 消除了 LiveData 的"粘性值"问题
🎨 Flow 操作符完全手册
常用操作符速查表
📝 数据变换类
| 操作符 | 功能 | 使用场景 | 代码示例 |
|---|---|---|---|
| map | 数据转换 | DTO → UI Model | .map { it.toUIModel() } |
| filter | 数据过滤 | 只要满足条件的 | .filter { it.age > 18 } |
| transform | 自定义变换 | 复杂转换 | .transform { emit(it * 2) } |
🔗 流合并类
| 操作符 | 特点 | 使用场景 |
|---|---|---|
| zip | 等待全部完成 | 多个独立请求,都需要 |
| combine | 实时组合 | 多个响应式数据源 |
| flatMapConcat | 顺序合并 | 依赖关系的任务 |
| flatMapLatest | 取最新值 | 搜索、自动完成 |
🛡️ 异常处理类
| 方式 | 使用场景 | 特点 |
|---|---|---|
| catch | 通用异常处理 | 可发送替代值 |
| retryWhen | 重试策略 | 支持条件重试 |
| onCompletion | 清理资源 | 成功/失败都执行 |
⚠️ 常见的坑(做过的人都踩过)
坑 1:忘记订阅 Flow
scss
// ❌ 错误:Flow 不会执行
viewModel.fetchUser("123")
// 什么都不会发生!
// ✅ 正确:必须调用 collect
viewModel.fetchUser("123")
.collect { user ->
updateUI(user)
}
记住: Flow 需要有"消费者"(collect)才会执行,这叫"冷流"。
坑 2:Flow 在主线程执行但没切换线程
scss
// ❌ 错误:这样会阻塞主线程!
viewModel.getUserFromDatabase()
.collect { user -> // ← 在主线程等待数据库查询
updateUI(user)
}
// ✅ 正确:指定线程
viewModel.getUserFromDatabase()
.flowOn(Dispatchers.IO) // ← 数据库操作放在 IO 线程
.collect { user ->
updateUI(user) // ← 这里还是主线程
}
关键字 :flowOn(Dispatchers.IO) 上游异步,collect 自动回到主线程。
坑 3:重复 collect 导致重复执行
scss
// ❌ 错误:订阅两次,执行两次网络请求
val userFlow = viewModel.fetchUser("123")
userFlow.collect { user1 ->
updateProfile(user1)
}
userFlow.collect { user2 ->
updateHeader(user2)
}
// 结果: 发出了 2 个相同的网络请求!浪费流量
// ✅ 正确方式 1:使用 SharedFlow(热流,共享结果)
val userFlow = viewModel.fetchUser("123")
.shareIn(viewModelScope, SharingStarted.Lazily, replay = 1)
userFlow.collect { updateProfile(it) }
userFlow.collect { updateHeader(it) }
// 结果: 只发出 1 个网络请求
// ✅ 正确方式 2:使用 StateFlow
private val _user = MutableStateFlow<User?>(null)
val user = _user.asStateFlow()
// 只需要 collect 一次状态值
通俗理解:
- 🧊
Flow= 冷流,每次订阅都重新执行(如:每次点击都重新查询) - 🔥
StateFlow/SharedFlow= 热流,所有订阅者共享结果(如:用户登录后所有页面都知道)
坑 4:忘记处理异常
scss
// ❌ 错误:异常被吞掉或导致崩溃
flow {
throw Exception("网络错误")
}.collect { data ->
updateUI(data) // ← 异常时永远不会执行
}
// ✅ 正确:用 catch 处理
flow {
throw Exception("网络错误")
}
.catch { exception ->
showError(exception.message)
}
.collect { data ->
updateUI(data)
}
最佳实践 :永远不要忘记 .catch { }
坑 5:ViewModel 卸载后 Flow 还在执行
scss
// ❌ 错误:ViewModel 被销毁,Flow 还在执行
flow {
repeat(100) {
delay(1000)
emit(it)
}
}.collect { value ->
println(value) // ← ViewModel 销毁后仍然执行,内存泄漏!
}
// ✅ 正确:使用 viewModelScope
viewModelScope.launch {
flow {
repeat(100) {
delay(1000)
emit(it)
}
}.collect { value ->
println(value) // ← ViewModel 销毁时自动取消
}
}
记住 :viewModelScope.launch { } 会自动取消任务。
📊 StateFlow vs SharedFlow 选择指南
场景决策树
markdown
你想维护一个状态值?
├─ 是 → StateFlow
│ └─ 用户信息、表单数据、UI 状态等
└─ 否 → SharedFlow
└─ 导航事件、消息提示等
完整对比
kotlin
// StateFlow:维护当前状态
private val _userState = MutableStateFlow<User?>(null)
val userState: StateFlow<User?> = _userState.asStateFlow()
fun login(email: String) {
viewModelScope.launch {
_userState.value = apiService.login(email) // ← 更新状态
}
}
// 使用
viewModel.userState.collect { user ->
if (user != null) {
showMainUI() // 用户已登录
} else {
showLoginUI() // 用户未登录
}
}
---
// SharedFlow:发送一次性事件
private val _navigationEvent = MutableSharedFlow<NavigationEvent>()
val navigationEvent: SharedFlow<NavigationEvent> = _navigationEvent.asSharedFlow()
fun goToDetail(itemId: Int) {
viewModelScope.launch {
_navigationEvent.emit(NavigationEvent.GoToDetail(itemId)) // ← 发送事件
}
}
// 使用
viewModel.navigationEvent.collect { event ->
when (event) {
is NavigationEvent.GoToDetail -> {
navigator.openDetailPage(event.itemId)
}
}
}
🔄 处理异常和重试的正确姿势
场景:网络请求失败时自动重试
方式 1:简单重试(重试 N 次)
kotlin
fun fetchUser(id: String) = flow {
emit(apiService.getUser(id))
}
.retry(3) // ← 失败重试 3 次
.catch { exception ->
Toast.show("重试 3 次后仍然失败: ${exception.message}")
}
问题:立即重试,没有延迟,容易 DDoS 服务器。
方式 2:延迟重试(指数退避)
kotlin
fun fetchUser(id: String) = flow {
emit(apiService.getUser(id))
}
.retryWhen { exception, attempt ->
// 第一次失败: 延迟 1秒
// 第二次失败: 延迟 2秒
// 第三次失败: 延迟 4秒
val delayMillis = (1000L shl attempt) // 2^attempt * 1000
delay(delayMillis)
attempt < 3 // ← 最多重试 3 次
}
.catch { exception ->
showError("网络连接失败,请检查网络")
}
输出时间线:
scss
第 1 次请求 ────► 失败 ──(等1秒)─→ 第 2 次请求 ────► 失败
──(等2秒)─→ 第 3 次请求 ────► 失败 ──(等4秒)─→ 第 4 次请求
方式 3:条件化重试(只重试某些错误)
kotlin
fun fetchUser(id: String) = flow {
emit(apiService.getUser(id))
}
.retryWhen { exception, attempt ->
when {
// 服务器错误,重试
exception is HttpException &&
exception.code() >= 500 -> {
delay(1000 * (attempt + 1))
true
}
// 客户端错误,不重试(肯定是请求格式错误)
exception is HttpException &&
exception.code() >= 400 -> {
false
}
// 网络错误,重试
exception is IOException -> {
delay(1000 * (attempt + 1))
attempt < 3
}
else -> false
}
}
🧪 单元测试:如何测试你的 Flow
快速 Flow 测试框架
kotlin
// 1. 添加依赖
dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test"
testImplementation "app.cash.turbine:turbine" // ← Flow 测试神器
}
// 2. 编写测试
@Test
fun testFetchUserSuccess() = runTest {
val viewModel = UserViewModel(mockApi)
viewModel.fetchUser("123").test {
// 验证第一个值
val user = awaitItem()
assertEquals("john", user.name)
// 验证流完成
awaitComplete()
}
}
@Test
fun testFetchUserError() = runTest {
val viewModel = UserViewModel(mockApiWithError)
viewModel.fetchUser("123").test {
val exception = awaitError()
assertTrue(exception is NetworkException)
}
}
Turbine 库的好处:
- ✅ 简洁的 API,易于理解
- ✅ 自动处理时间控制
- ✅ 支持多个 Flow 的并行测试
🚀 快速参考:常用代码片段
场景 1:加载列表(带加载状态)
kotlin
sealed class UiState<T> {
class Loading<T> : UiState<T>()
data class Success<T>(val data: T) : UiState<T>()
data class Error<T>(val message: String) : UiState<T>()
}
class ListViewModel : ViewModel() {
fun loadItems(): Flow<UiState<List<Item>>> = flow {
emit(UiState.Loading())
try {
val items = apiService.getItems()
emit(UiState.Success(items))
} catch (e: Exception) {
emit(UiState.Error(e.message ?: "未知错误"))
}
}
}
// UI 层
viewModel.loadItems().collect { state ->
when (state) {
is UiState.Loading -> showProgressBar()
is UiState.Success -> showItems(state.data)
is UiState.Error -> showError(state.message)
}
}
场景 2:搜索 + 分页
kotlin
fun searchItems(query: Flow<String>, page: Int): Flow<List<Item>> = query
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { searchTerm ->
flow {
val results = apiService.search(searchTerm, page)
emit(results)
}
}
.catch { emitAll(emptyFlow()) }
场景 3:定时任务
scss
fun startHeartbeat(): Flow<Long> = flow {
while (true) {
emit(System.currentTimeMillis())
delay(5000) // 每 5 秒发送一次心跳
}
}
// 使用
viewModel.startHeartbeat()
.take(10) // 只取前 10 个
.collect { timestamp ->
sendHeartbeatToServer(timestamp)
}
📱 在 Android 项目中集成 Flow 的最佳实践
项目架构推荐
kotlin
app/
├── ui/
│ ├── activities/
│ │ └── MainActivity.kt
│ ├── fragments/
│ └── viewmodels/
│ └── UserViewModel.kt ← 使用 Flow 和 StateFlow
├── data/
│ ├── api/
│ │ └── ApiService.kt ← 返回 Flow 或 suspend fun
│ ├── database/
│ │ └── UserDao.kt ← 返回 Flow
│ ├── repository/
│ │ └── UserRepository.kt ← 组合多个 Flow
│ └── models/
│ └── User.kt
└── di/
└── AppModule.kt ← 依赖注入
各层的职责
kotlin
// 📡 API 层(网络请求)
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: String): User
}
// 🗂️ DAO 层(数据库)
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
fun getUser(id: String): Flow<User>
}
// 📦 Repository 层(数据聚合)
class UserRepository(
private val apiService: ApiService,
private val userDao: UserDao
) {
fun getUser(id: String): Flow<User> = flow {
try {
// 优先返回本地数据
userDao.getUser(id).collect { localUser ->
emit(localUser)
}
// 然后从网络更新
val remoteUser = apiService.getUser(id)
userDao.insertUser(remoteUser) // 更新本地
emit(remoteUser)
} catch (e: Exception) {
throw e
}
}
}
// 🎯 ViewModel 层(UI 数据管理)
class UserViewModel(private val repository: UserRepository) : ViewModel() {
fun fetchUser(id: String): Flow<User> = repository.getUser(id)
.catch { exception ->
println("加载失败: ${exception.message}")
emitAll(emptyFlow())
}
}
// 📱 UI 层(展示)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.fetchUser("123").collect { user ->
updateUI(user)
}
}
}
}
}
💼 团队协作建议
1. 统一规范
kotlin
// ❌ 不同的返回方式,容易混乱
fun getUserA(): User { } // 同步
fun getUserB(callback: (User) -> Unit) { } // 回调
fun getUserC(): Flow<User> { } // Flow
// ✅ 统一用 Flow(或 suspend)
suspend fun getUser(id: String): User { } // 网络请求
fun getUser(id: String): Flow<User> { } // 数据库/长时间任务
建议:
- 网络请求用
suspend函数 - 数据库查询返回
Flow - 长时间任务用
Flow - ViewModel 暴露
StateFlow<T>或Flow<T>
2. Code Review 检查清单
arduino
□ Flow 有没有被订阅(collect)?
□ 异常处理完了吗(catch)?
□ 在正确的线程执行吗(flowOn)?
□ ViewModel 销毁会自动取消吗(viewModelScope)?
□ 重复订阅会导致重复执行吗?
□ 单元测试覆盖了吗?
3. 常见的代码评审问题
scss
// ❌ Review 驳回:没有异常处理
viewModel.fetchUser("123").collect { user ->
updateUI(user)
}
// ✅ Review 通过:加上异常处理
viewModel.fetchUser("123")
.catch { exception ->
showError(exception.message)
}
.collect { user ->
updateUI(user)
}
---
// ❌ Review 驳回:没有指定线程
val users = databaseDao.getAllUsers()
.collect { users ->
updateUI(users) // 可能在 IO 线程!
}
// ✅ Review 通过:指定了线程
val users = databaseDao.getAllUsers()
.flowOn(Dispatchers.IO) // 数据库操作放在 IO 线程
.collect { users ->
updateUI(users) // 自动切回主线程
}
📚 推荐学习资源
官方资源
深入学习
- 《Kotlin Coroutines: Deep Dive》 by Roman Elizarov
- GitHub: Learn-Kotlin-Flow
测试工具
- Turbine:Flow 测试库
- kotlinx-coroutines-test:协程测试框架
🔥 核心速记表
Flow 操作符一览
scss
创建: flow { }, flowOf(), MutableSharedFlow(), MutableStateFlow()
转换: map(), filter(), transform()
合并: zip(), combine(), flatMapConcat(), flatMapLatest()
处理: catch(), retry(), retryWhen(), onCompletion()
限流: debounce(), throttle(), distinctUntilChanged()
消费: collect(), launchIn(), collectAsState()
快速决策
| 问题 | 答案 |
|---|---|
| 每次订阅都要重新执行吗? | 是 → Flow,否 → StateFlow/SharedFlow |
| 需要维护当前状态吗? | 是 → StateFlow,否 → SharedFlow |
| 担心网络请求失败吗? | 加 .catch { } |
| 线程管理问题吗? | 加 .flowOn(Dispatchers.IO) |
| 需要重试吗? | 加 .retry(n) 或 .retryWhen { } |
❓ FAQ(常见问题)
Q1: Flow 会比 LiveData 多占用多少内存?
A: 基本没区别,Flow 更轻量。区别在于功能强大程度。
Q2: 什么时候该用 suspend 函数而不是 Flow?
A:
- 一次性网络请求 →
suspend fun - 持续监听数据库/数据源 →
Flow<T>
Q3: 如何取消一个正在执行的 Flow?
A : 使用 viewModelScope 会自动取消,或手动调用 cancel()。
Q4: Flow 对性能有影响吗?
A: 反而更优化!避免了多层回调和线程频繁切换。
Q5: 老项目全是 RxJava,要全部迁移吗?
A: 不用全部迁移,新代码用 Flow,逐步替换即可。
🎯 总结:为什么 Flow 是未来
| 特性 | RxJava | LiveData | Flow |
|---|---|---|---|
| 学习曲线 | 陡 😰 | 平 😊 | 平 😊 |
| 功能强大 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 与 Coroutine 融合 | ❌ | 中等 | ✅ 完美 |
| 官方推荐 | ❌ | 渐淡 | ✅ 重点 |
| 心智成本 | 高 | 低 | 低 |
结论:
Flow 是 Kotlin 异步编程的未来。不仅功能强大,而且与官方 Coroutines 完美配套。现在是学习 Flow 的最好时机。
祝你 Flow 学习之旅顺利!
一旦掌握 Flow,你会发现异步编程原来可以这么优雅。