Kotlin中的Flow基本应用

132 阅读2分钟

Kotlin 中的 Flow 是一个用于处理异步数据流的抽象,它是 Kotlin 协程库的一部分。Flow 提供了一种声明性的方式来处理序列化的异步数据,如从数据库、网络请求或任何其他异步数据源中获取的数据。Flow 使得处理这些数据流变得更加简洁和高效。
1,基本概念:
Flow:一个冷数据流,它表示一个异步的、序列化的值 T 的流。
发射(Emit):Flow 通过发射(emit)函数来产生数据项。
收集(Collect):Flow 的数据项通过 collect 函数来消费。
Flow 是一个冷数据流,这意味着它只有在被收集(collect)时才会开始执行。这与热数据流(如 LiveData 或 RxJava 的 Observable)不同,后者即使没有观察者也会继续发射数据。

2,创建Flow与收集Flow:
使用 flow{} 构建器创建,并可以使用asFlow()扩展函数将现有的集合或序列转换为 Flow。
使用 collect 函数来消费 Flow 中的数据。collect 是一个挂起函数,需要在协程作用域内调用。

val flowA = flow  {
    emit(1)
    emit(2)
    emit(3)
    emit("hello")
    emit("world")
}
val list = listOf(1,2,3,4,"Jacky","Jerry")
val flowB = list.asFlow()

runBlocking {
    flowA.collect { valueA ->
        println(valueA)
    }

    flowB.collect { valueB ->
        println(valueB)
    }
}

Flow 的生命周期与它的收集者紧密相关。一旦收集者被取消或完成,Flow 的执行也会相应停止。
使用 withTimeout 在超时后取消 Flow 的代码示例:

val list = listOf(1,2,3,4,"Jacky","Mary")
val flowB = list.asFlow()

runBlocking {
    try {
        withTimeout(3000L) {
            flowB.collect { valueB ->
                delay(2000L)
                println(valueB)
            }
        }
    } catch (e:TimeoutCancellationException) {
        println("TimeoutCancellationException...")
    }
}

3,Flow的操作符
Flow 提供了丰富的操作符,用于转换和组合数据流。
3.1,转换操作符,包括 map,filter,take,drop等。 map:将 Flow 中的每个值转换为另一个值。

import kotlinx.coroutines.flow.*
   
runBlocking {
    val numbers = flowOf(1, 2, 3, 4, 5)
    val squaredNumbers = numbers.map { it * it }
    squaredNumbers.collect { println(it) } // 输出: 1, 4, 9, 16, 25
}

filter:只允许满足特定条件的值通过 Flow。

runBlocking {
    val numbers = flowOf(1, 2, 3, 4, 5)
    val squaredNumbers = numbers.filter { it % 2 == 0 }
    squaredNumbers.collect { println(it) } // 输出: 2, 4
}

take:从 Flow 中获取指定数量的值。

runBlocking {
    val numbers = flowOf(1, 2, 3, 4, 5)
    val evenNumbers = numbers.take(3)
    evenNumbers.collect { println(it) } // 输出: 1, 2, 3
}

drop:跳过 Flow 中的指定数量的值。

runBlocking {
    val numbers = flowOf(1, 2, 3, 4, 5)
    val evenNumbers = numbers.drop(3)
    evenNumbers.collect { println(it) } // 输出: 4, 5
}

3.2,组合操作符,包括 zip、merge、combine、flattenMerge 等。
zip:将两个 Flow 的值按对组合起来。

	runBlocking {
        val numbers = flowOf(1, 2, 3)
        val letters = flowOf('a', 'b', 'c')
        val zipped = numbers.zip(letters) { num, letter -> "$num$letter" }
        zipped.collect { println(it) } // 输出: 1a, 2b, 3c
        val zippedB = numbers.zip(letters) { num, letter -> "$letter$num$letter" }
        zippedB.collect { println(it) } // 输出: a1a, b2b, c3c
    }

merge:将两个或多个 Flow 的值合并到一个 Flow 中,值的顺序不确定。

	runBlocking {
        val flow1 = flowOf(1, 3, 5).onEach { delay(100) } // 模拟延迟
        val flow2 = flowOf(2, 4, 6).onEach { delay(150) }
        val merged = merge(flow1, flow2)
        merged.collect { println(it) } // 输出顺序可能是: 1, 2, 3, 4, 5, 6 或其他
    }

combine:将多个 Flow 的最新值组合起来,并在任何一个 Flow 发射新值时更新结果。

	runBlocking {
        val flowA = flowOf(1, 2).map { "A$it" }
        val flowB = flowOf(10, 20).map { "B$it" }
        val combined = combine(flowA, flowB) { a, b -> "$a and $b" }
        combined.collect { println(it) } // 输出: A1 and B10, A2 and B20
    }

flattenMerge:将一个发射 Flow 的 Flow 转换为一个包含所有内部 Flow 值的单个 Flow。

	runBlocking {
        val flowOfFlows = flowOf(
            flowOf(1, 2),
            flowOf(3, 4, 5)
        )
        val flattened = flowOfFlows.flattenMerge()
        flattened.collect { println(it) } // 输出: 1, 2, 3, 4, 5(顺序不确定)
    }

上面的代码中,
runBlocking是一个挂起函数,它启动一个新的协程作用域并阻塞当前线程,直到该作用域内的所有协程都完成。这是为了在阻塞线程环境中(如main函数)运行挂起函数(如Flow的collect操作)的常用方式。
flowOf(flowOf(1, 2), flowOf(3, 4, 5))创建了一个Flow的Flow,即一个包含两个内部Flow的外部Flow。第一个内部Flow发出1和2,第二个内部Flow发出3、4和5。
flattenMerge()是一个操作符,用于将嵌套的Flow展平成一个单一的Flow,并合并来自所有内部Flow的元素。
与flattenConcat()不同,flattenMerge()不会保持来自内部Flow的元素顺序,而是并发地收集它们,因此最终输出的顺序是不确定的。

3.3,flattenConcat:
flattenConcat 是 Kotlin 协程中 Flow API 的一部分,用于将嵌套的 Flow(即 Flow 的 Flow)展平成一个单一的 Flow,并顺序地连接来自内部 Flow 的元素。
与 flattenMerge 不同,flattenConcat 会保持内部 Flow 发出元素的顺序,即它会先收集完第一个内部 Flow 的所有元素,然后再开始收集第二个内部 Flow 的元素,以此类推。

	runBlocking {
        // 创建一个嵌套的 Flow,包含两个内部 Flow
        val flowOfFlows: Flow<Flow<Int>> = flowOf(
            flowOf(1, 2).flowOn(Dispatchers.IO), // 在 IO 调度器上运行
            flowOf(3, 4, 5).flowOn(Dispatchers.Default) // 在 Default 调度器上运行
        )

        // 使用 flattenConcat 展平嵌套的 Flow
        val flattened: Flow<Int> = flowOfFlows.flattenConcat()

        // 收集并打印展平后的 Flow 中的元素
        flattened.collect { value ->
            println("Collected: $value")
        }
    }

上面的代码中, 创建了一个 flowOfFlows,它是一个包含两个内部 Flow 的外部 Flow。每个内部 Flow 都在不同的协程调度器上运行(Dispatchers.IO 和 Dispatchers.Default), 但这并不影响 flattenConcat 的行为,因为 flattenConcat 关心的是元素的顺序,而不是它们在哪个线程或调度器上被发出。