Android Kotlin(3) Flow异步流

一、为什么需要 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 的核心优势

  1. 基于协程:天然支持 suspend 函数,无缝集成 Kotlin 协程。
  2. 冷流(Cold Stream) :只有订阅时才执行,节省资源。
  3. 结构化并发:自动遵循协程作用域,避免内存泄漏。
  4. 丰富的操作符 :类似 RxJava,但更符合 Kotlin 习惯(如 map, filter, combine, flatMapLatest 等)。
  5. 支持背压:通过挂起机制自然处理背压(不像 RxJava 需要显式策略)。
  6. 可与 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():将集合、序列转为 Flow
  • callbackFlow { }:桥接回调 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 中的操作是顺序执行 的,即使有 delayemit

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 { } 基础构建器,支持 emitsuspend 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 中间结果)
  1. 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)
                )
            }
        }
}
  1. 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
  1. 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 将冷流转热流
相关推荐
三少爷的鞋2 小时前
为什么你的 SharedFlow 不工作?深挖这 3 个关键参数
android
凛_Lin~~2 小时前
安卓 面试八股文整理(原理与性能篇)
android·java·面试·安卓
花花鱼3 小时前
android 更新后安装app REQUEST_INSTALL_PACKAGES 权限受限 + FileProvider 元数据异常
android
2501_946233893 小时前
Flutter与OpenHarmony大师详情页面实现
android·javascript·flutter
z9209810234 小时前
ZTE 中兴 高通 安卓手机 一键改串 一键新机 IMEI MEID 写号 硬改 手机修改参数 视频教程演示
android·智能手机
idealzouhu4 小时前
【Android Framework】Intent 运行机制
android
2501_946233894 小时前
Flutter与OpenHarmony Tab切换组件开发详解
android·javascript·flutter
2501_946233894 小时前
Flutter与OpenHarmony订单详情页面实现
android·javascript·flutter
2501_944446004 小时前
Flutter&OpenHarmony拖拽排序功能实现
android·javascript·flutter