一、为什么需要 Flow?------ 从 LiveData 和 RxJava 说起
核心问题
LiveData不是协程原生,无法处理复杂异步逻辑RxJava学习曲线陡峭,且与协程混用易出错- 需要一种冷流(Cold Stream) 、结构化并发安全 、自动生命周期感知的方案
kotlin
// LiveData 基本使用
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun loadData() {
_data.value = "Hello"
}
}
// Activity 中观察
viewModel.data.observe(this) { data ->
// UI 更新
}
// ❌无法处理多个连续异步事件
fun fetchData() {
viewModelScope.launch {
val data1 = api.fetchData1() // 挂起函数
_data1.value = data1
val data2 = api.fetchData2() // 第二个请求
_data2.value = data2
}
}
// ❌ 快速发射数据时,旧值会被覆盖
fun rapidUpdates() {
repeat(100) { i ->
_data.value = "Value $i" // 只有最后一个值被接收
}
}
// ❌ 线程切换不够灵活,默认在主线程更新
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchData() // IO 线程
_data.postValue(data) // 必须用 postValue
}
// ❌ 组合能力有限,难以组合多个数据源
val liveData1: LiveData<A>
val liveData2: LiveData<B>
// 合并需要 Transformations.switchMap
val result = Transformations.switchMap(liveData1) { a ->
Transformations.map(liveData2) { b ->
a to b
}
}
kotlin
// RxJava 处理复杂异步流
Observable.interval(1, TimeUnit.SECONDS)
.map { it * 2 }
.filter { it % 4 == 0 }
.flatMap { api.fetchData(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ data -> updateUI(data) },
{ error -> showError(error) }
)
// ❌RxJava 的复杂性
Observable.create<String> { emitter ->
// 手动管理资源
val subscription = someApi.subscribe { data ->
emitter.onNext(data)
}
emitter.setCancellable { subscription.unsubscribe() }
}
.map { ... }
.flatMap { ... }
.switchOnNext { ... }
// ❌内存泄漏风险,需要手动管理生命周期
val disposables = CompositeDisposable()
disposables.add(
api.getData()
.subscribe { data -> /* 处理数据 */ }
)
// 必须记得清理
override fun onDestroy() {
disposables.clear()
super.onDestroy()
}
Flow 的核心优势
- 基于协程:天然支持 suspend 函数,无缝集成 Kotlin 协程。
- 冷流(Cold Stream) :只有订阅时才执行,节省资源。
- 结构化并发:自动遵循协程作用域,避免内存泄漏。
- 丰富的操作符 :类似 RxJava,但更符合 Kotlin 习惯(如
map,filter,combine,flatMapLatest等)。 - 支持背压:通过挂起机制自然处理背压(不像 RxJava 需要显式策略)。
- 可与 LiveData 互转 :
flow.asLiveData()用于 UI 层。
示例:用 Flow 实现相同逻辑(更简洁、安全)
kotlin
class MyRepository {
fun fetchDataFlow(): Flow<List<Item>> = flow {
emit(repository.fetchFromDb()) // 可以是 Room 的 Flow 查询
val remote = apiService.getItems() // suspend call
emit(mergeWithRemote(remote))
}.flowOn(Dispatchers.IO) // 指定上游运行在 IO 线程
}
// ViewModel 中
class MyViewModel : ViewModel() {
val uiState: LiveData<UiState> = repository.fetchDataFlow()
.map { UiState.Success(it) }
.catch { emit(UiState.Error(it.message)) }
.asLiveData() // 转为 LiveData 供 UI 观察
}
二、Flow 基础三要素
1. 创建 Flow
flow { }:最常用,支持emit()和suspend函数channelFlow { }:高级,支持在不同协程中 emit(热流雏形)asFlow():将集合、序列转为 FlowcallbackFlow { }:桥接回调 API(如传感器、Firebase)
kotlin
val numberFlow = flow {
for (i in 1..3) {
delay(100)
emit(i) // 挂起点
}
}
2. 中间操作符(可链式)
map,filter,onEach,transform,take,drop,debounce,distinctUntilChanged...- 注意:这些操作符本身不触发执行(冷流特性)
kotlin
numberFlow
.map { it * 2 }
.filter { it > 2 }
.onEach { println("Processing: $it") }
3. 终端操作符(触发执行)
collect { }:最基础launchAndCollectIn(scope):Android KTX 扩展(已弃用,改用repeatOnLifecycle)toList(),first(),single():用于测试或单次获取
kotlin
lifecycleScope.launch {
numberFlow.collect { value ->
textView.text = value.toString()
}
}
三、返回多个值-集合-序列-挂起函数
1. 返回多个值的传统方式
| 方式 | 特点 | 问题 |
|---|---|---|
List<T> |
一次性返回所有值 | 必须等全部数据准备好,内存占用高 |
Sequence<T> |
惰性求值,同步生成 | 不能包含 suspend 函数,无法处理异步 |
suspend fun List<T> |
异步获取完整列表 | 仍需等待全部完成,无法"边算边发" |
2. 集合 (Collection) - 一次性返回所有值
kotlin
// List - 立即计算并返回所有值
fun getCitiesList(): List<String> {
println("开始计算列表...")
val result = mutableListOf<String>()
for (i in 1..3) {
Thread.sleep(1000) // 模拟耗时计算
result.add("City$i")
println("添加 City$i")
}
return result
}
fun main() {
val cities = getCitiesList() // 这里就执行了所有计算
println("列表计算完成")
cities.forEach { println("访问: $it") }
}
缺点:
- 必须等待所有值计算完成才能返回
- 占用内存存储所有元素
- 不能表示"正在进行中"的状态
3. 序列 (Sequence) - 惰性返回多个值
kotlin
// Sequence - 惰性计算
fun getCitiesSequence(): Sequence<String> = sequence {
println("开始序列计算...")
for (i in 1..3) {
Thread.sleep(1000) // 模拟耗时计算
yield("City$i")
println("产生 City$i")
}
}
fun main() {
val sequence = getCitiesSequence() // 这里不执行计算
println("序列创建完成")
sequence.forEach {
println("访问: $it") // 每次访问时才计算下一个
}
}
优点:
- 惰性计算,按需生成
- 节省内存
- 可以表示无限序列
缺点:
kotlin
// ❌ 序列中不能调用挂起函数
fun getDataSequence(): Sequence<String> = sequence {
for (i in 1..3) {
delay(1000) // 编译错误!不能在序列中使用挂起函数
yield("Data$i")
}
}
4. 挂起函数 (suspend) - 返回单个值
kotlin
// suspend 函数 - 返回单个值
suspend fun fetchUserData(): User {
delay(1000) // 模拟网络请求
return User("John", 25)
}
// 返回 List - 仍然是单个返回值
suspend fun fetchAllUsers(): List<User> {
delay(1000)
return listOf(User("John"), User("Jane"))
}
fun main() = runBlocking {
val user = fetchUserData() // 等待1秒后得到单个用户
println("用户: $user")
val users = fetchAllUsers() // 等待1秒后得到所有用户
println("所有用户: $users")
}
缺点:
kotlin
// ❌ 无法在过程中"推送"数据
suspend fun fetchUsersStream(): ??? {
// 无法做到:收到一个用户就通知一次
// 必须等所有用户都获取完成后一次性返回
}
四、通过 Flow 异步返回多个值
- 像写同步代码一样,安全地发射异步序列值
kotlin
// Flow - 异步流式返回
fun getCitiesFlow(): Flow<String> = flow {
println("Flow 开始发射")
for (i in 1..3) {
delay(1000) // ✅ 可以调用挂起函数
emit("City$i")
println("发射 City$i")
}
}
suspend fun main() {
println("开始收集")
getCitiesFlow()
.onEach { println("处理: $it") }
.collect {
println("收集到: $it")
}
println("收集完成")
}
优点:
- 支持
suspend函数(如网络、数据库) - 冷流:不 collect 就不执行
- 自动协程取消(结构化并发)
五、Flow 与其他方式的区别
| 特性 | List |
Sequence |
Channel |
Flow |
|---|---|---|---|---|
| 同步/异步 | 同步 | 同步 | 异步 | 异步 |
| 支持 suspend | ❌ | ❌ | ✅ | ✅ |
| 冷/热 | N/A | 冷 | 热 | 冷 |
| 自动取消 | ❌ | ❌ | 手动管理 | ✅(结构化并发) |
| 背压处理 | ❌ | ❌ | 需手动缓冲 | ✅(buffer(), conflate()) |
六、Flow 的一个典型应用场景
场景:实时搜索(Debounced Search)
用户输入关键词,自动去服务器搜索,但要防抖、防重复请求。
kotlin
class SearchViewModel : ViewModel() {
private val _query = MutableStateFlow("")
val results = _query
.debounce(300) // 防抖:300ms 内无新输入才触发
.distinctUntilChanged() // 相同查询不重复请求
.flatMapLatest { query -> // 只保留最新查询的结果
if (query.isBlank()) flowOf(emptyList())
else searchRepository.search(query)
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
}
✅ 效果:
- 用户快速打字 "a" → "ab" → "abc",只发起一次 "abc" 请求
- 上一个未完成的请求自动取消(
flatMapLatest的魔力)
七、什么是冷流(Cold Stream)
冷流:每次被 collect 时,都会重新执行整个流构建逻辑。
kotlin
val coldFlow = flow {
println("Flow started!")
emit(1)
emit(2)
}
// 第一次收集
coldFlow.collect { println("A: $it") }
// 输出:
// Flow started!
// A: 1
// A: 2
// 第二次收集
coldFlow.collect { println("B: $it") }
// 输出:
// Flow started! ← 重新执行!
// B: 1
// B: 2
🔁 对比热流(如 StateFlow):
- 热流只有一个生产者,多个观察者共享同一份数据流
- 冷流每次 collect 都是独立执行
- (有积累地去创业)
✅ 冷流适合:每次都需要重新计算的数据(如网络请求、数据库查询)
六、流的连续性(Sequential Execution)
Flow 中的操作是顺序执行 的,即使有 delay 或 emit:
kotlin
flow {
emit("A")
delay(1000)
emit("B")
}
.map { it.lowercase() }
.onEach { println("Processed: $it") }
.collect { println("Collected: $it") }
输出(按时间顺序):
text
Processed: a
Collected: a
(1秒后)
Processed: b
Collected: b
⚠️ 注意:中间操作符不会并行执行!这是 Flow 的设计哲学:简单、可预测。
八、流构建器(Flow Builders)
| 构建器 | 用途 | 示例 |
|---|---|---|
flow { } |
基础构建器,支持 emit 和 suspend |
flow { emit(fetchData()) } |
flowOf() |
从固定值创建 Flow | flowOf(1, 2, 3) |
asFlow() |
将集合/序列转为 Flow | (1..5).asFlow() |
channelFlow { } |
高级:可在不同协程中 emit(热流雏形) | 见下文 |
callbackFlow { } |
桥接回调 API(如传感器、Firebase) | ✅ Android 常用 |
callbackFlow 示例(监听位置更新)
kotlin
fun locationFlow(context: Context) = callbackFlow<Location> {
val listener = LocationListener { location ->
trySend(location) // 安全发送(不会因取消而崩溃)
}
val locationManager = context.getSystemService(LOCATION_SERVICE) as LocationManager
locationManager.requestLocationUpdates(..., listener)
// 取消时清理
awaitClose {
locationManager.removeUpdates(listener)
}
}
九、流上下文(Flow Context)
默认情况下,Flow 构建器中的代码运行在调用者的协程上下文中。
kotlin
lifecycleScope.launch(Dispatchers.Main) {
flow {
println("Thread: ${Thread.currentThread().name}") // Main
emit(1)
}.collect { ... }
}
但可以用 flowOn 改变上游操作的调度器:
kotlin
flow {
println("IO Thread: ${Thread.currentThread().name}")
emit(fetchFromNetwork()) // 耗时操作
}
.flowOn(Dispatchers.IO) // ← 改变 flow {} 的执行线程
.map { it.toUiModel() }
.collect { /* 在 Main 线程 */ }
- 数据获取用
flowOn(Dispatchers.IO) - UI 转换和 collect 在
Main线程
十、在指定协程中收集流
收集(collect)总是在启动协程的上下文中执行:
kotlin
// 在 IO 线程收集
withContext(Dispatchers.IO) {
myFlow.collect {
// 这里也在 IO 线程
}
}
// 在 Main 线程收集(Android 推荐)
lifecycleScope.launch(Dispatchers.Main) {
myFlow.collect { updateUI(it) } // 安全更新 UI
}
重要:不要在非主线程更新 UI;
十一、流的取消
Flow 收集是可取消的,遵循协程的协作式取消机制。
kotlin
val job = lifecycleScope.launch {
countDownFlow().collect { ... }
}
// 1秒后取消
lifecycleScope.launch {
delay(1000)
job.cancel()
}
当 job.cancel() 被调用:
collect立即停止- Flow 构建器中的
emit后续不再执行 - 如果正在
delay(),会抛出CancellationException(静默处理)
十二、流的取消检测
在自定义 Flow 中,若执行长时间计算(非挂起函数),需主动检测取消:
kotlin
flow {
for (i in 1..1_000_000) {
// 主动检查是否被取消
if (currentCoroutineContext().isActive.not()) {
break
}
emit(expensiveCalculation(i))
}
}
或使用 ensureActive()(推荐):
kotlin
flow {
for (i in 1..1_000_000) {
ensureActive() // 若已取消,抛出 CancellationException
emit(i)
}
}
这样即使没有挂起点,也能响应取消。
十三、使用缓冲与 flowOn 处理背压(Backpressure)
背压场景:
生产者(emit)速度 > 消费者(collect)速度
kotlin
val fastProducer = flow {
repeat(100) {
delay(10) // 每 10ms 发一个
emit(it)
}
}
fastProducer.collect {
delay(1000) // 每 1s 处理一个 → 背压!
}
解决方案:
1. buffer():启用缓冲通道(默认容量 64)
kotlin
fastProducer
.buffer() // 生产者可继续 emit,值存入缓冲区
.collect { ... }
2. conflate():只保留最新值(丢弃中间值)
kotlin
sensorDataFlow
.conflate() // UI 只关心最新传感器数据,旧的可丢
.collect { updateChart(it) }
3. collectLatest:取消前一个收集块
kotlin
searchQueries
.mapLatest { query -> api.search(query) } // 自动取消上一个搜索
.collect { showResults(it) }
flowOn 也会隐式启用缓冲,避免阻塞上游。
十四、合并与处理最新值
| 操作符 | 行为 | 适用场景 |
|---|---|---|
merge() |
合并多个 Flow,谁 emit 谁先处理 | 多个独立数据源 |
combine() |
当任一 Flow emit,组合最新值 | 表单验证(username + password) |
zip() |
成对 emit(第一个 Flow 的第 n 个 + 第二个的第 n 个) | 严格同步数据 |
flatMapLatest |
对每个值启动新 Flow,只保留最新的 | 搜索、自动补全 |
1. merge()- 合并多个独立流
kotlin
class HomeViewModel : ViewModel() {
// 多个独立的数据源
private val userFlow = repository.observeUser()
private val messagesFlow = repository.observeMessages()
private val notificationsFlow = repository.observeNotifications()
val allUpdates = merge(
userFlow.map { "用户更新: $it" },
messagesFlow.map { "新消息: ${it.count()} 条" },
notificationsFlow.map { "通知: $it" }
)
// 处理不同类型的更新
init {
viewModelScope.launch {
allUpdates.collect { update ->
when {
update.startsWith("用户更新") -> handleUserUpdate(update)
update.startsWith("新消息") -> showMessageBadge(update)
update.startsWith("通知") -> showNotification(update)
}
}
}
}
}
2. combine - 组合最新值
kotlin
val isLoginEnabled = combine(
usernameFlow,
passwordFlow
) { username, password ->
username.isNotBlank() && password.length >= 6
}
3. zip()- 成对组合
kotlin
// 两个 API 请求的结果需要配对处理
val userFlow = flow {
emit("User1")
delay(1000)
emit("User2")
delay(1000)
emit("User3")
}
val scoreFlow = flow {
emit(95)
delay(500)
emit(88)
delay(500)
emit(92)
delay(500)
emit(85) // 这个值不会被处理,因为 userFlow 只有 3 个值
}
fun main() = runBlocking {
userFlow.zip(scoreFlow) { user, score ->
"$user: $score points"
}.collect { result ->
println(result)
}
}
4. flatMapLatest- 处理最新值
kotlin
// 搜索功能实现
class SearchViewModel : ViewModel() {
val searchQuery = MutableStateFlow("")
val searchResults = searchQuery
.debounce(300) // 防抖
.filter { it.length >= 2 } // 过滤短查询
.distinctUntilChanged() // 去重
.flatMapLatest { query ->
performSearch(query) // 对每个查询启动搜索
.catch { emit(emptyList()) } // 错误处理
.onStart { emit(emptyList()) } // 显示加载状态
}
private fun performSearch(query: String): Flow<List<SearchResult>> = flow {
// 模拟网络请求
delay(1000)
val results = searchApi.search(query)
emit(results)
}
// 在 UI 中收集
fun observeResults(): Flow<List<SearchResult>> = searchResults
}
// 使用示例
viewModelScope.launch {
searchResults.collect { results ->
updateSearchResults(results)
}
}
十五、转换操作符
| 操作符 | 说明 |
|---|---|
map |
一对一转换 |
transform |
手动控制 emit(可 emit 多个或跳过) |
scan |
累积计算(类似 reduce,但 emit 中间结果) |
map- 一对一转换
kotlin
// 网络数据模型 -> UI 数据模型
data class UserResponse(
val id: Int,
val name: String,
val email: String,
val createdAt: String
)
data class UserUI(
val id: Int,
val displayName: String,
val email: String,
val joinDate: String
)
class UserViewModel : ViewModel() {
val users: Flow<List<UserUI>> = userRepository.getUsers()
.map { userList ->
// 批量转换
userList.map { response ->
UserUI(
id = response.id,
displayName = response.name.capitalize(),
email = response.email,
joinDate = formatDate(response.createdAt)
)
}
}
}
transform- 手动控制 emit
kotlin
// transform 的灵活性
flowOf(1, 2, 3, 4, 5)
.transform { value ->
if (value % 2 == 0) {
emit("偶数: $value") // 可以 emit 不同类型
emit("double: ${value * 2}") // 可以 emit 多个值
}
// 跳过奇数值
}
.collect { println(it) }
// 输出:
// 偶数: 2
// double: 4
// 偶数: 4
// double: 8
scan- 累积计算
kotlin
// 累加计算
flowOf(1, 2, 3, 4, 5)
.scan(0) { accumulator, value ->
accumulator + value
}
.collect { println("当前累加和: $it") }
// 输出:
// 当前累加和: 0
// 当前累加和: 1
// 当前累加和: 3
// 当前累加和: 6
// 当前累加和: 10
// 当前累加和: 15
// 与 reduce 对比
flowOf(1, 2, 3, 4, 5)
.reduce { acc, value -> acc + value } // 只输出最终结果: 15
十六、限长操作符
| 操作符 | 作用 |
|---|---|
take(n) |
只取前 n 个值 |
takeWhile { } |
满足条件就取,一旦不满足就停止 |
drop(n) |
跳过前 n 个 |
first() / single() |
终端操作,返回单个值 |
十七、末端操作符
| 操作符 | 说明 |
|---|---|
collect { } |
基础收集 |
toList() |
收集成 List(测试常用) |
first() |
获取第一个值并取消 Flow |
reduce { } / fold { } |
聚合所有值 |
launchIn(scope) |
在指定作用域启动收集(已弃用,改用 repeatOnLifecycle) |
十八、组合操作符
见第十四(合并与最新值),此处不再重复。
十八、展平操作符
用于处理 Flow<Flow> 结构:
| 操作符 | 行为 |
|---|---|
flattenConcat() |
串行展开(等第一个 Flow 结束再处理第二个) |
flattenMerge() |
并行展开(同时 collect 所有子 Flow) |
flatMapConcat() |
map + flattenConcat |
flatMapMerge() |
map + flattenMerge |
flatMapLatest() |
map + 只保留最新子 Flow ✅ 最常用 |
flatMapLatest 再次强调(搜索场景):
kotlin
queries
.flatMapLatest { query ->
api.search(query) // 返回 Flow<SearchResult>
}
.collect { results ->
updateUI(results)
}
当用户输入 "a" → "ab" → "abc":
-
"a" 的请求发出
-
输入 "ab",自动取消 "a" 的请求,发出 "ab"
-
输入 "abc",自动取消 "ab" 的请求,发出 "abc"
十九、流的异常处理
Flow 中的异常处理有两种方式:
1. 在收集端 try-catch(推荐)
kotlin
try {
api.dataFlow.collect { ... }
} catch (e: IOException) {
showError(e.message)
}
2. 使用 catch 操作符(可 emit 兜底值)
kotlin
api.dataFlow
.catch { e ->
if (e is IOException) {
emit(emptyData()) // 提供默认值
} else {
throw e // 重新抛出
}
}
.collect { render(it) }
3. 重试:retry, retryWhen
kotlin
api.dataFlow
.retry(3) { it is IOException }
.collect { ... }
注意:catch 只能捕获上游异常 ,不能捕获 collect 块内的异常!
二十、流的完成(Completion)
Flow 正常结束时,可通过 onCompletion 执行清理:
kotlin
counterFlow
.onCompletion { cause ->
if (cause == null) {
println("Flow completed normally")
} else {
println("Flow failed: $cause")
}
}
.collect { ... }
cause == null→ 正常结束(emit 完毕)cause is CancellationException→ 被取消cause is Exception→ 发生错误
适合做日志、资源释放等收尾工作。
二十一、生命周期安全 repeatOnLifecycle + lifecycleScope
1. 为什么需要"生命周期安全
2. 错误做法
3. 正确做法:repeatOnLifecycle + lifecycleScope
总结
| 概念 | 关键点 |
|---|---|
| 冷流 | 每次 collect 重新执行 |
| 结构化并发 | 自动取消,避免泄漏 |
| 背压处理 | buffer(), conflate(), collectLatest |
| 生命周期安全 | repeatOnLifecycle + lifecycleScope |
| 异常处理 | catch + retry + 收集端 try-catch |
| 热流升级 | 用 stateIn / shareIn 将冷流转热流 |