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 将冷流转热流
相关推荐
andr_gale25 分钟前
04_rc文件语法规则
android·framework·aosp
祖国的好青年1 小时前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
黄林晴2 小时前
警惕!AGP 9.2 别只改版本号,R8 规则与构建链路全线收紧
android·gradle
小米渣的逆袭2 小时前
Android ADB 完全使用指南
android·adb
儿歌八万首2 小时前
Jetpack Compose Canvas 进阶:结合 animateFloatAsState 让自定义图形动起来
android·动画·compose
zhangphil3 小时前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
神探小白牙3 小时前
echarts,3d堆叠图
android·3d·echarts
李白的天不白4 小时前
如何项目发布到github上
android·vue.js
summerkissyou19874 小时前
Android-RTC、NTP 和 System Time(系统时间)
android