【Kotlin系列11】协程原理与实战(下):Flow与Channel驯服异步数据流

引言:异步数据流的烦恼

假设你正在开发一个股票交易应用,需要实时显示股票价格:

kotlin 复制代码
// 传统方式:轮询 + 回调
class StockMonitor {
    fun startMonitoring(symbol: String, callback: (Double) -> Unit) {
        timer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                val price = fetchStockPrice(symbol)
                callback(price)
            }
        }, 0, 1000)  // 每秒查询一次
    }
}

// 使用时
stockMonitor.startMonitoring("AAPL") { price ->
    runOnUiThread {
        updateUI(price)
    }
}

这种方式有什么问题?

  1. 资源浪费:即使价格没变化也要轮询
  2. 内存泄漏:忘记取消timer
  3. 线程混乱:需要手动切换线程
  4. 难以组合:监听多个股票?处理数据流?

Kotlin Flow优雅地解决了这些问题:

kotlin 复制代码
// 使用Flow:响应式数据流
fun stockPriceFlow(symbol: String): Flow<Double> = flow {
    while (true) {
        val price = fetchStockPrice(symbol)
        emit(price)  // 发射数据
        delay(1000)  // 挂起1秒
    }
}

// 使用时
lifecycleScope.launch {
    stockPriceFlow("AAPL")
        .filter { it > 150.0 }  // 过滤
        .map { "$$it" }         // 转换
        .collect { price ->     // 收集
            updateUI(price)
        }
}  // 自动取消,无需手动管理

优势明显

  • ✅ 自动生命周期管理
  • ✅ 线程自动切换
  • ✅ 函数式操作符
  • ✅ 背压处理

本文将深入讲解Flow和Channel,让你彻底掌握协程的异步数据流处理。

Flow基础:响应式数据流

什么是Flow?

Flow 是Kotlin协程提供的异步数据流API,类似于RxJava的Observable,但:

  • 基于协程:天然支持挂起函数
  • 冷流:只有收集时才开始执行
  • 结构化并发:自动管理生命周期
  • 背压支持:内置背压处理机制

创建Flow

kotlin 复制代码
import kotlinx.coroutines.flow.*

// 1. flow构建器 - 最基础的方式
fun simpleFlow(): Flow<Int> = flow {
    println("Flow开始")
    for (i in 1..3) {
        delay(1000)
        emit(i)  // 发射数据
    }
}

// 2. flowOf - 固定数据
val numbersFlow = flowOf(1, 2, 3, 4, 5)

// 3. asFlow - 集合转Flow
val listFlow = listOf(1, 2, 3).asFlow()

// 4. channelFlow - 支持并发发射
fun concurrentFlow(): Flow<Int> = channelFlow {
    launch {
        repeat(3) {
            delay(1000)
            send(it)  // 使用send而非emit
        }
    }
}

收集Flow

kotlin 复制代码
fun main() = runBlocking {
    val flow = simpleFlow()

    // 收集方式1: collect - 最基础
    flow.collect { value ->
        println("收到: $value")
    }

    // 收集方式2: toList - 收集到列表
    val list = flow.toList()
    println("列表: $list")

    // 收集方式3: first - 只取第一个
    val first = flow.first()
    println("第一个: $first")

    // 收集方式4: reduce - 聚合
    val sum = flow.reduce { acc, value -> acc + value }
    println("总和: $sum")
}

// 输出:
// Flow开始
// 收到: 1
// 收到: 2
// 收到: 3

Flow是冷流

Flow是冷流(Cold Stream),意味着:

kotlin 复制代码
fun main() = runBlocking {
    val flow = flow {
        println("Flow开始执行")
        emit(1)
        emit(2)
    }

    println("Flow创建完成")
    delay(1000)
    println("准备收集")

    flow.collect { println("值: $it") }
}

// 输出:
// Flow创建完成
// 准备收集(1秒后)
// Flow开始执行  ← 注意:这里才开始执行
// 值: 1
// 值: 2

冷流特点

  • 只有调用collect才开始执行
  • 每次收集都重新执行
  • 不同收集器互不影响
kotlin 复制代码
fun main() = runBlocking {
    val flow = flow {
        println("Flow执行")
        emit(Random.nextInt())
    }

    // 两次收集,Flow执行两次
    flow.collect { println("收集器1: $it") }
    flow.collect { println("收集器2: $it") }
}

// 输出:
// Flow执行
// 收集器1: 12345
// Flow执行  ← 再次执行
// 收集器2: 67890

Flow操作符

转换操作符

kotlin 复制代码
// map - 转换每个元素
flow {
    emit(1)
    emit(2)
    emit(3)
}.map { it * 2 }
 .collect { println(it) }  // 2, 4, 6

// filter - 过滤
flow {
    emit(1)
    emit(2)
    emit(3)
}.filter { it % 2 == 0 }
 .collect { println(it) }  // 2

// transform - 自定义转换(可以emit多次)
flow {
    emit(1)
    emit(2)
}.transform { value ->
    emit("开始: $value")
    delay(100)
    emit("结束: $value")
}.collect { println(it) }
// 输出:
// 开始: 1
// 结束: 1
// 开始: 2
// 结束: 2

// flatMapConcat - 顺序展平
flowOf(1, 2, 3)
    .flatMapConcat { value ->
        flow {
            emit("$value-a")
            delay(100)
            emit("$value-b")
        }
    }
    .collect { println(it) }
// 输出:1-a, 1-b, 2-a, 2-b, 3-a, 3-b

// flatMapMerge - 并发展平(最多并发数)
flowOf(1, 2, 3)
    .flatMapMerge(concurrency = 2) { value ->
        flow {
            delay(100)
            emit("$value")
        }
    }
    .collect { println(it) }
// 输出顺序不定(并发执行)

限流操作符

kotlin 复制代码
// take - 只取前N个
flowOf(1, 2, 3, 4, 5)
    .take(3)
    .collect { println(it) }  // 1, 2, 3

// drop - 跳过前N个
flowOf(1, 2, 3, 4, 5)
    .drop(2)
    .collect { println(it) }  // 3, 4, 5

// debounce - 防抖(只取最后一个)
flow {
    emit(1)
    delay(90)
    emit(2)
    delay(90)
    emit(3)
    delay(1000)
    emit(4)
}.debounce(100)  // 100ms内的连续emit,只保留最后一个
 .collect { println(it) }  // 3, 4

// sample - 采样(固定时间取一个)
flow {
    repeat(10) {
        emit(it)
        delay(110)
    }
}.sample(300)  // 每300ms采样一个
 .collect { println(it) }  // 0, 2, 5, 8

组合操作符

kotlin 复制代码
// zip - 配对组合
val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("A", "B", "C")
flow1.zip(flow2) { a, b -> "$a-$b" }
    .collect { println(it) }  // 1-A, 2-B, 3-C

// combine - 组合最新值(任一更新都emit)
val numbers = flow {
    emit(1)
    delay(100)
    emit(2)
}
val strings = flow {
    emit("A")
    delay(150)
    emit("B")
}
numbers.combine(strings) { num, str -> "$num-$str" }
    .collect { println(it) }
// 输出:1-A, 2-A, 2-B

展平操作符对比

kotlin 复制代码
// flatMapConcat - 顺序执行
// Flow1 → [a, b] → Flow2 → [c, d]
// 结果: a, b, c, d(顺序)

// flatMapMerge - 并发执行
// Flow1 → [a, b]
// Flow2 → [c, d]  并发
// 结果: a, c, b, d(乱序)

// flatMapLatest - 只保留最新
// Flow1 → [a, b]
// Flow2 → [c, d]  ← 新Flow取消旧Flow
// 结果: a, c, d(b被取消)

异常处理

catch操作符

kotlin 复制代码
flow {
    emit(1)
    emit(2)
    throw RuntimeException("出错了!")
    emit(3)  // 不会执行
}.catch { e ->
    println("捕获异常: ${e.message}")
    emit(-1)  // 可以发射默认值
}.collect { println(it) }

// 输出:
// 1
// 2
// 捕获异常: 出错了!
// -1

注意catch只能捕获上游的异常:

kotlin 复制代码
flow {
    emit(1)
    emit(2)
}.catch { e ->
    println("不会执行")
}.collect { value ->
    throw RuntimeException("这个异常catch捕获不到")
}

onCompletion

kotlin 复制代码
flow {
    emit(1)
    emit(2)
}.onCompletion { cause ->
    if (cause != null) {
        println("Flow异常完成: $cause")
    } else {
        println("Flow正常完成")
    }
}.collect { println(it) }

// 输出:
// 1
// 2
// Flow正常完成

retry与retryWhen

kotlin 复制代码
var attempts = 0

flow {
    attempts++
    println("尝试 $attempts")
    if (attempts < 3) {
        throw IOException("网络错误")
    }
    emit("成功")
}.retry(3) { cause ->
    (cause is IOException).also {
        if (it) delay(1000)  // 重试前等待1秒
    }
}.collect { println(it) }

// 输出:
// 尝试 1
// 尝试 2
// 尝试 3
// 成功

线程切换与上下文

flowOn操作符

kotlin 复制代码
fun loadData(): Flow<String> = flow {
    println("Flow运行在: ${Thread.currentThread().name}")
    emit(fetchFromNetwork())
}.flowOn(Dispatchers.IO)  // ← 上游运行在IO线程

fun main() = runBlocking {
    loadData()
        .map { data ->
            println("map运行在: ${Thread.currentThread().name}")
            data.uppercase()
        }
        .flowOn(Dispatchers.Default)  // ← map运行在Default线程
        .collect { data ->
            println("collect运行在: ${Thread.currentThread().name}")
            updateUI(data)
        }  // collect运行在Main线程
}

重要规则

  • flowOn影响上游操作符
  • 可以多次调用flowOn切换不同上下文
  • collect运行在调用协程的上下文

StateFlow:可观察状态

StateFlow vs Flow

StateFlow是Flow的特殊版本,特点:

  1. 热流:始终有值,立即发射当前状态
  2. 状态保持:保存最新值
  3. 去重:相同值不会重复发射
  4. 线程安全:可以从多个协程读写
kotlin 复制代码
// StateFlow示例
class CounterViewModel {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count.asStateFlow()

    fun increment() {
        _count.value++  // 更新状态
    }
}

// 使用
val viewModel = CounterViewModel()

// 收集器1
launch {
    viewModel.count.collect { count ->
        println("收集器1: $count")
    }
}

// 收集器2
launch {
    viewModel.count.collect { count ->
        println("收集器2: $count")
    }
}

viewModel.increment()  // 两个收集器都会收到更新

StateFlow的特点

kotlin 复制代码
val stateFlow = MutableStateFlow("初始值")

// 1. 立即发射当前值
launch {
    stateFlow.collect { println(it) }  // 立即输出:初始值
}

// 2. 去重(相同值不会重复emit)
stateFlow.value = "新值"   // ✅ emit
stateFlow.value = "新值"   // ❌ 不emit(值相同)
stateFlow.value = "更新值" // ✅ emit

// 3. 获取当前值
println("当前值: ${stateFlow.value}")

StateFlow实战:用户状态管理

kotlin 复制代码
sealed class UserState {
    object Loading : UserState()
    data class Success(val user: User) : UserState()
    data class Error(val message: String) : UserState()
}

class UserRepository {
    private val _userState = MutableStateFlow<UserState>(UserState.Loading)
    val userState: StateFlow<UserState> = _userState.asStateFlow()

    suspend fun loadUser(userId: String) {
        _userState.value = UserState.Loading

        try {
            val user = api.fetchUser(userId)
            _userState.value = UserState.Success(user)
        } catch (e: Exception) {
            _userState.value = UserState.Error(e.message ?: "未知错误")
        }
    }
}

// ViewModel
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {
    val userState = repository.userState
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = UserState.Loading
        )

    fun loadUser(userId: String) {
        viewModelScope.launch {
            repository.loadUser(userId)
        }
    }
}

// UI收集
lifecycleScope.launch {
    viewModel.userState.collect { state ->
        when (state) {
            is UserState.Loading -> showLoading()
            is UserState.Success -> showUser(state.user)
            is UserState.Error -> showError(state.message)
        }
    }
}

SharedFlow:事件广播

SharedFlow vs StateFlow

特性 StateFlow SharedFlow
保存值 ✅ 保存最新值 ❌ 可配置(replay)
立即发射 ✅ 新收集器立即收到当前值 ❌ 只收到新事件
去重 ✅ 相同值不重复emit ❌ 不去重
适用场景 状态管理 事件通知
kotlin 复制代码
// SharedFlow示例
class EventBus {
    private val _events = MutableSharedFlow<String>()
    val events: SharedFlow<String> = _events.asSharedFlow()

    suspend fun sendEvent(event: String) {
        _events.emit(event)
    }
}

val eventBus = EventBus()

// 收集器1
launch {
    eventBus.events.collect { event ->
        println("收集器1收到: $event")
    }
}

delay(100)

// 收集器2(稍后订阅)
launch {
    eventBus.events.collect { event ->
        println("收集器2收到: $event")
    }
}

eventBus.sendEvent("事件1")  // 两个收集器都收到

SharedFlow配置

kotlin 复制代码
val sharedFlow = MutableSharedFlow<Int>(
    replay = 2,        // 保留最近2个值给新订阅者
    extraBufferCapacity = 10,  // 额外缓冲容量
    onBufferOverflow = BufferOverflow.DROP_OLDEST  // 缓冲满时丢弃最旧的
)

// 发射5个值
repeat(5) { sharedFlow.emit(it) }

// 新订阅者会收到最近2个值(3和4)
sharedFlow.collect { println(it) }  // 3, 4, ...

SharedFlow实战:Toast通知

kotlin 复制代码
class ToastManager {
    private val _toastMessages = MutableSharedFlow<String>(
        replay = 0,  // 不保留历史消息
        extraBufferCapacity = 10,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val toastMessages: SharedFlow<String> = _toastMessages.asSharedFlow()

    fun showToast(message: String) {
        // tryEmit不挂起,适合从非协程代码调用
        _toastMessages.tryEmit(message)
    }
}

// Activity中收集
class MainActivity : AppCompatActivity() {
    private val toastManager = ToastManager()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            toastManager.toastMessages.collect { message ->
                Toast.makeText(this@MainActivity, message, Toast.LENGTH_SHORT).show()
            }
        }
    }
}

Channel:协程间通信

Channel基础

Channel是协程之间通信的管道,类似于阻塞队列:

kotlin 复制代码
val channel = Channel<Int>()

// 发送方协程
launch {
    for (x in 1..5) {
        println("发送: $x")
        channel.send(x)  // 挂起直到有接收方
    }
    channel.close()  // 关闭通道
}

// 接收方协程
launch {
    for (x in channel) {  // 迭代接收
        println("接收: $x")
        delay(1000)  // 模拟处理
    }
}

Channel类型

kotlin 复制代码
// 1. Rendezvous(默认)- 无缓冲
val channel1 = Channel<Int>()
// 发送方挂起,直到接收方ready

// 2. Buffered - 有缓冲
val channel2 = Channel<Int>(10)
// 缓冲未满时不挂起

// 3. Unlimited - 无限缓冲
val channel3 = Channel<Int>(Channel.UNLIMITED)
// send永不挂起(可能OOM)

// 4. Conflated - 保留最新
val channel4 = Channel<Int>(Channel.CONFLATED)
// 只保留最新值,旧值被覆盖

Channel vs Flow

kotlin 复制代码
// Channel示例
val channel = Channel<Int>()
launch {
    channel.send(1)
    channel.send(2)
}
launch {
    println(channel.receive())  // 1
}
launch {
    println(channel.receive())  // 2(被另一个协程接收)
}

// Flow示例
val flow = flowOf(1, 2)
launch {
    flow.collect { println(it) }  // 1, 2
}
launch {
    flow.collect { println(it) }  // 1, 2(每个收集器独立)
}

对比

特性 Channel Flow
消费 热流,多消费者竞争 冷流,每个收集器独立
背压 通过缓冲和挂起 内置背压处理
关闭 需要手动关闭 自动管理
适用场景 协程间通信 数据流处理

Channel实战:生产者消费者

kotlin 复制代码
// 生产者
fun produceNumbers() = produce<Int> {
    var x = 1
    while (true) {
        send(x++)
        delay(100)
    }
}

// 消费者
fun CoroutineScope.squareNumbers(numbers: ReceiveChannel<Int>) = produce<Int> {
    for (x in numbers) {
        send(x * x)
    }
}

// 使用
fun main() = runBlocking {
    val numbers = produceNumbers()
    val squares = squareNumbers(numbers)

    repeat(5) {
        println(squares.receive())
    }

    coroutineContext.cancelChildren()  // 取消所有
}

// 输出:1, 4, 9, 16, 25

管道模式

kotlin 复制代码
// 管道函数
fun CoroutineScope.pipeline(
    input: ReceiveChannel<Int>
) = produce<String> {
    for (num in input) {
        send("Processed: $num")
    }
}

// 使用管道
fun main() = runBlocking {
    val numbers = produce {
        repeat(5) { send(it) }
    }

    val processed = pipeline(numbers)

    for (result in processed) {
        println(result)
    }
}

背压处理

什么是背压?

背压(Backpressure):生产者速度快于消费者时的处理策略。

kotlin 复制代码
// 问题:生产快,消费慢
flow {
    repeat(100) {
        println("发射: $it")
        emit(it)
        delay(10)  // 快速发射
    }
}.collect { value ->
    println("收集: $value")
    delay(100)  // 慢速收集
}

Flow的背压策略

1. buffer - 缓冲
kotlin 复制代码
flow {
    repeat(100) {
        emit(it)
        delay(10)
    }
}.buffer(50)  // 缓冲50个元素
 .collect { value ->
     delay(100)
     println(value)
 }
2. conflate - 合并(只保留最新)
kotlin 复制代码
flow {
    repeat(100) {
        emit(it)
        delay(10)
    }
}.conflate()  // 消费慢时,跳过中间值
 .collect { value ->
     delay(100)
     println(value)
 }
// 输出可能是:0, 10, 20, ..., 90(跳过中间值)
3. collectLatest - 只处理最新
kotlin 复制代码
flow {
    repeat(100) {
        emit(it)
        delay(10)
    }
}.collectLatest { value ->
    println("开始处理: $value")
    delay(100)
    println("处理完成: $value")  // 可能被中断
}
// 输出:只有最后几个值被完整处理

对比三种策略

kotlin 复制代码
// buffer:缓冲所有,按顺序处理
flow {
    (1..5).forEach {
        delay(100)
        emit(it)
    }
}.buffer()
 .collect {
     delay(300)
     println(it)  // 1, 2, 3, 4, 5(全部处理)
 }

// conflate:跳过中间值
flow {
    (1..5).forEach {
        delay(100)
        emit(it)
    }
}.conflate()
 .collect {
     delay(300)
     println(it)  // 1, 4, 5(跳过2, 3)
 }

// collectLatest:只处理最新值
flow {
    (1..5).forEach {
        delay(100)
        emit(it)
    }
}.collectLatest {
    println("开始: $it")
    delay(300)
    println("完成: $it")  // 只有5完成
}

实战案例:实时搜索

让我们构建一个实时搜索功能,结合debounce和distinctUntilChanged:

kotlin 复制代码
class SearchViewModel : ViewModel() {
    private val _searchQuery = MutableStateFlow("")

    // 搜索结果Flow
    val searchResults: StateFlow<List<String>> = _searchQuery
        .debounce(300)  // 防抖:300ms内的连续输入只取最后一次
        .distinctUntilChanged()  // 去重:相同查询不重复搜索
        .filter { it.length >= 2 }  // 过滤:至少2个字符
        .flatMapLatest { query ->  // 切换:新查询取消旧查询
            flow {
                emit(emptyList())  // 先清空结果
                emit(performSearch(query))  // 执行搜索
            }
        }
        .catch { e ->
            emit(emptyList())  // 错误时返回空列表
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )

    fun updateQuery(query: String) {
        _searchQuery.value = query
    }

    private suspend fun performSearch(query: String): List<String> {
        delay(500)  // 模拟网络请求
        return listOf("$query - 结果1", "$query - 结果2")
    }
}

// UI层
class SearchActivity : AppCompatActivity() {
    private val viewModel: SearchViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 监听输入
        searchEditText.addTextChangedListener { text ->
            viewModel.updateQuery(text.toString())
        }

        // 收集结果
        lifecycleScope.launch {
            viewModel.searchResults.collect { results ->
                updateSearchResults(results)
            }
        }
    }
}

这个实现的优势

  1. 防抖:避免频繁搜索
  2. 去重:相同查询不重复
  3. 取消旧请求:新查询自动取消旧请求
  4. 异常处理:错误时返回空列表
  5. 自动管理:生命周期自动管理

高级技巧

1. 组合多个Flow

kotlin 复制代码
// 场景:用户信息 + 好友列表 + 帖子列表
class ProfileViewModel : ViewModel() {
    private val userId = MutableStateFlow("user123")

    val profileData = combine(
        userId.flatMapLatest { id -> loadUser(id) },
        userId.flatMapLatest { id -> loadFriends(id) },
        userId.flatMapLatest { id -> loadPosts(id) }
    ) { user, friends, posts ->
        ProfileData(user, friends, posts)
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = ProfileData.Loading
    )
}

2. Flow的测试

kotlin 复制代码
class FlowTest {
    @Test
    fun `test flow emissions`() = runTest {
        val flow = flow {
            emit(1)
            delay(100)
            emit(2)
            delay(100)
            emit(3)
        }

        val results = flow.toList()
        assertEquals(listOf(1, 2, 3), results)
    }

    @Test
    fun `test StateFlow`() = runTest {
        val stateFlow = MutableStateFlow(0)

        val job = launch {
            stateFlow.emit(1)
            delay(100)
            stateFlow.emit(2)
        }

        val results = stateFlow.take(3).toList()
        assertEquals(listOf(0, 1, 2), results)

        job.cancel()
    }
}

3. 自定义操作符

kotlin 复制代码
// 自定义操作符:mapNotNull
fun <T, R> Flow<T>.mapNotNull(
    transform: suspend (T) -> R?
): Flow<R> = transform { value ->
    transform(value)?.let { emit(it) }
}

// 使用
flowOf(1, 2, 3, 4, 5)
    .mapNotNull { if (it % 2 == 0) it * 2 else null }
    .collect { println(it) }  // 4, 8

常见陷阱与最佳实践

❌ 陷阱1:在collect中调用emit

kotlin 复制代码
// ❌ 错误:collect中不能调用emit
flow {
    emit(1)
}.collect { value ->
    emit(value * 2)  // ❌ 编译错误
}

// ✅ 正确:使用transform
flow {
    emit(1)
}.transform { value ->
    emit(value * 2)  // ✅ transform中可以
}.collect { println(it) }

❌ 陷阱2:忘记切换线程

kotlin 复制代码
// ❌ 错误:IO操作在Main线程
fun loadData(): Flow<String> = flow {
    val data = fetchFromNetwork()  // ❌ 阻塞Main线程
    emit(data)
}

// ✅ 正确:使用flowOn切换线程
fun loadData(): Flow<String> = flow {
    val data = fetchFromNetwork()
    emit(data)
}.flowOn(Dispatchers.IO)  // ✅ IO操作在IO线程

❌ 陷阱3:StateFlow不会emit相同值

kotlin 复制代码
val stateFlow = MutableStateFlow(1)

stateFlow.value = 2  // ✅ emit
stateFlow.value = 2  // ❌ 不emit(值相同)

// 解决方案1:使用SharedFlow
val sharedFlow = MutableSharedFlow<Int>()
sharedFlow.emit(2)  // ✅ emit
sharedFlow.emit(2)  // ✅ 仍然emit

// 解决方案2:强制更新
data class State(val value: Int, val timestamp: Long = System.currentTimeMillis())
val stateFlow = MutableStateFlow(State(1))
stateFlow.value = State(2)  // ✅ emit(timestamp不同)

✅ 最佳实践1:使用sealed class表示状态

kotlin 复制代码
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String) : UiState<Nothing>()
}

class ViewModel {
    private val _uiState = MutableStateFlow<UiState<User>>(UiState.Loading)
    val uiState: StateFlow<UiState<User>> = _uiState.asStateFlow()

    fun loadUser() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val user = repository.loadUser()
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "未知错误")
            }
        }
    }
}

✅ 最佳实践2:使用stateIn转换Flow为StateFlow

kotlin 复制代码
// ❌ 避免:每次collect都重新执行
fun loadUsers(): Flow<List<User>> = flow {
    emit(repository.getUsers())  // 每次collect都查询数据库
}

// ✅ 推荐:使用stateIn共享结果
val users: StateFlow<List<User>> = loadUsers()
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = emptyList()
    )

✅ 最佳实践3:使用shareIn实现热流

kotlin 复制代码
// 场景:多个订阅者共享同一个网络请求
val sharedData: SharedFlow<Data> = flow {
    val data = fetchFromNetwork()  // 只执行一次
    emit(data)
}.shareIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(),
    replay = 1  // 新订阅者会收到最后一个值
)

性能优化

1. 选择合适的Flow类型

kotlin 复制代码
// 状态 → StateFlow
val userState: StateFlow<User> = ...

// 事件 → SharedFlow
val clickEvents: SharedFlow<Unit> = ...

// 一次性数据流 → Flow
fun loadData(): Flow<Data> = flow { ... }

2. 使用buffer提高吞吐量

kotlin 复制代码
// 无buffer:生产者等待消费者
flow {
    repeat(100) {
        emit(it)
        println("发射: $it")
    }
}.collect { value ->
    delay(10)
    println("收集: $value")
}
// 总耗时:约1000ms(100 * 10ms)

// 有buffer:生产者不等待
flow {
    repeat(100) {
        emit(it)
        println("发射: $it")
    }
}.buffer(10)
 .collect { value ->
     delay(10)
     println("收集: $value")
 }
// 总耗时:约100ms(并发执行)

3. 避免不必要的操作符

kotlin 复制代码
// ❌ 避免:多次map
flowOf(1, 2, 3)
    .map { it * 2 }
    .map { it + 1 }
    .map { it.toString() }

// ✅ 推荐:合并操作
flowOf(1, 2, 3)
    .map { (it * 2 + 1).toString() }

小结

本文深入讲解了Kotlin协程的高级特性Flow和Channel:

核心概念

  1. Flow:冷流,异步数据流,支持丰富的操作符
  2. StateFlow:热流,可观察状态,去重,线程安全
  3. SharedFlow:热流,事件广播,支持多订阅者
  4. Channel:协程间通信管道,支持多种缓冲策略

关键技能

  1. 创建Flow:flow、flowOf、asFlow、channelFlow
  2. 转换操作符:map、filter、transform、flatMap
  3. 异常处理:catch、onCompletion、retry
  4. 背压处理:buffer、conflate、collectLatest
  5. 线程切换:flowOn切换上下文
  6. 状态管理:StateFlow保存状态

最佳实践

  • ✅ 使用sealed class表示UI状态
  • ✅ 使用stateIn/shareIn共享Flow
  • ✅ 合理选择Flow类型(StateFlow vs SharedFlow)
  • ✅ 使用flowOn切换线程
  • ✅ 使用catch处理异常
  • ✅ 合理使用背压策略

下期预告

下一篇文章《函数式编程在Kotlin中的实践》将介绍:

  • 高阶函数:函数作为参数和返回值
  • Lambda表达式:简洁的函数式编程
  • 扩展函数:增强类的能力
  • 内联函数:性能优化
  • 函数式集合操作:map、filter、reduce

练习题

基础练习

练习1:实现一个倒计时Flow

kotlin 复制代码
fun countdownFlow(from: Int): Flow<Int> {
    // TODO: 从from倒数到0,每秒发射一个值
}

练习2:实现一个温度转换Flow

kotlin 复制代码
fun celsiusToFahrenheit(celsius: Flow<Double>): Flow<Double> {
    // TODO: 将摄氏度转换为华氏度
}

进阶练习

练习3:实现一个带重试的网络请求Flow

kotlin 复制代码
fun <T> flowWithRetry(
    maxRetries: Int = 3,
    delayMillis: Long = 1000,
    block: suspend () -> T
): Flow<T> {
    // TODO: 失败时自动重试
}

练习4:实现一个聊天消息管理器

kotlin 复制代码
class ChatManager {
    // TODO: 使用StateFlow管理消息列表
    // TODO: 使用SharedFlow广播新消息事件
}

参考资料


系列文章导航:


如果这篇文章对你有帮助,欢迎点赞、收藏、分享!有任何问题或建议,欢迎在评论区留言讨论。让我们一起学习,一起成长!

也欢迎访问我的个人主页发现更多宝藏资源

相关推荐
好大哥呀2 小时前
如何在手机上运行Python程序
开发语言·python·智能手机
阿蒙Amon2 小时前
C#每日面试题-is和as的区别
java·开发语言·c#
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于Python的新闻热点舆情分析系统为例,包含答辩的问题和答案
开发语言·python
XerCis2 小时前
Python代码检查与格式化工具Ruff
开发语言·python
少控科技2 小时前
QT高阶日记010
开发语言·qt
秦jh_2 小时前
【Qt】界面优化
开发语言·qt
阿蒙Amon2 小时前
C#每日面试题-简述泛型约束
java·开发语言·c#
zh_xuan2 小时前
kotlin 延迟属性
开发语言·kotlin
_昨日重现2 小时前
Jetpack系列之Compose Scaffold
android·android jetpack