Kotlin学习笔记28 Flow part2 Flow引入 Flow的执行 取消 构建器 中间操作符 终端操作符 默认执行顺序 上下文相关

参考链接

示例来自bilibili Kotlin语言深入解析 张龙老师的视频

1  一个方法返回多个结果各种实现

/**
 * 一个方法返回多个结果
 * 方式1 返回一个集合
 * 特点
 * 1 方法本身是阻塞的,即主线程会进入该方法内部执行,一直执行到方法结束
 * 2 集合本身是一次性返回给调用者的,即集合中的全部元素均已经获得之后才统一返回给调用端
 */
private fun myMethod(): List = listOf("hello", "world", "welcome")

fun main() {
    myMethod().forEach { println(it) }
}

class HelloKotlin1 {
}



/**
 * 一个方法返回多个结果
 * 方式2 返回一个序列(sequence)
 * 如果在获取每一个结果时都需要执行一定的计算,这种计算是一种阻塞行为,每计算出一个结果就立即返回给调用端
 * 特点
 * 1 序列中的数据并不像集合那样一次性返回,而是计算完一个数据后就返回
 * 2 序列中的计算过程会占用主线程执行,因此会阻塞主线程
 */
private fun myMethod(): Sequence = sequence {
    for (i in 100..105) {
        Thread.sleep(100)
        yield(i)
    }
}

fun main() {
    myMethod().filter { it % 2 == 0 }.map { it -> "result: $it" }.forEach { println(it) }
}

class HelloKotlin2 {
}






/**
 * 一个方法返回多个结果
 * 方式3 返回一个集合(结合协程)
 * 特点
 * 1 不会阻塞主线程
 * 2 集合本身是一次性返回给调用者的
 */
private suspend fun myMethod(): List {
    delay(100)
    return listOf("hello", "world", "welcome")
}

fun main() = runBlocking {
    myMethod().forEach { println(it) }
}

class HelloKotlin3 {
}






/**
 * 一个方法返回多个结果
 * 方式4 返回一个Flow
 *
 * 如果返回一个List 那么代表只能一次性返回所有的值。要想能够像sequence一样一次返回一个值
 * 并且还不会阻塞主线程,可以使用Flow类型
 *
 * 特点
 * 1 Flow的创建使用flow构建器
 * 2 位于flow构建器中的代码是可以挂起的 而不需要额外的suspend关键字
 * 3 使用emit函数发射函数
 * 4 Flow里面的值通过collect来收集
 *
 * flow简单介绍
 * flow是一种冷异步数据流,它按顺序发出值,正常或异常完成
 *
 * flow的构建器有如下几种
 * flowOf(...) functions to create a flow from a fixed set of values.
 * asFlow() extension functions on various types to convert them into flows.
 * flow { ... } builder function to construct arbitrary flows from sequential calls to emit function.
 * channelFlow { ... } builder function to construct arbitrary flows from potentially concurrent calls to the send function.
 *
 * flow有中间操作符 如map、filter、take、zip 等
 * 中间操作不会执行流程中的任何代码,也不会挂起函数本身
 * flow还有终端操作符 flow上的终端操作符要么是暂停函数,如 collect、single、reduce、toList 等,
 * 要么是启动给定范围内流的收集的 launchIn 操作符
 *
 */

private fun myMethod(): Flow = flow {
    for (i in 1..4) {
        delay(100)
        //Thread.sleep(100) //sleep1
        emit(i)
    }
}

fun main() = runBlocking {
    launch {
        for (i in 1..4) {
            println("hello in launch $i")
            delay(200)
            //Thread.sleep(100) //sleep2
            // sleep方法不管加在那里 都会导致并行运行失效 因为Thread.sleep(100)需要睡眠主线程
            // 因此加了sleep的方法就类似变成了非挂起函数
        }
    }
    myMethod().collect {
        println(it)
    }
    // 这里launch里面的输出和myMethod().collect并行运行
}

class HelloKotlin4 {
}

2  Flow的执行

/**
 * Flow的执行
 * 
 * Flow 只有执行了终端操作符之后(例如collect) flow才会真正执行
 * 例如下面 我们调用myMethod方法后 myMethod立即返回 但是其中的代码不会运行
 */
private fun myMethod(): Flow = flow {
    println("myMethod execute")
    for (i in 1..4) {
        delay(100)
        emit(i)
    }
}

fun main() = runBlocking {
    println("enter")
    val flow = myMethod()//只调用这个方法 不会执行myMethod内部的内容
    println("1111")
    flow.collect {
        println(it)
    }
    println("2222")
}

class HelloKotlin5 {
}

3  Flow的取消

/**
 * Flow的取消
 * Flow实际上没有提供取消的方法 Flow的取消依赖于协程,如果Flow依附的协程取消了,则Flow也会取消
 *
 * Flow的收集操作是可以取消的,前提是Flow在一个可以取消挂起函数(如delay)中被挂起了,除此之外,我们无法
 * 通过其他方式取消Flow
 */

private fun myMethod(): Flow = flow{
    for (i in 1..4){
        delay(100)
        println("emit $i")
        emit(i)
    }
}

fun main() = runBlocking {
    // 协程超过280毫秒会被取消
    withTimeoutOrNull(280) {// 如果改成withTimeout会抛出TimeoutCancellationException
        myMethod().collect {
            println(it)
        }
    }
    println("finished")
}

class HelloKotlin6 {
}

4 Flow builder(流构建器)

/**
 * Flow builder(流构建器)
 * 1 flow构建器 是经常被使用的流构建器
 * 2 flowOf构建器 可以用于定义能够发射固定数量值的流
 * 3 asFlow构建器 对于各种集合与序列来说,他们都提供了asFlow()扩展方法来将自身转换为Flow
 *
 *
 *
 * 非局部返回
 * inline,noinline,crossinline
 * non-local reutrns:非局部返回 实际上表示的是在一个方法内部,我们可以在其中通过一个lambda表达式的返回来直接将外层方法返回返回
 * crossinline的作用实际上表示被标记的lambda是禁止非局部返回?
 */

fun main() = runBlocking {
    // asFlow构建器可以将各种集合转为Flow 例如 LongRange IntRange IntArray Array Sequence
    (1..10).asFlow().collect {
        println(it)
    }

    println("-------")

    // flowOf构建器可以用于定义能够发射固定数量值 flowOf的参数是一个可变参数
    flowOf(10,20,3,4,50).collect {
        println(it)
    }
}

class HelloKotlin7 {
}

5 Flow 的中间操作符filter map

/**
 * Flow 的中间操作符filter map
 * Sequence也可以调用filter map等方法
 * Flow与Sequence之间的区别:对于Flow来说 中间运算符代码内可以调用挂起函数(对比Kotlin2)
 */
private suspend fun myExecution(input: Int): String {
    println("myExecution: $input")
    delay(500)
    return "output: $input"
}

fun main() = runBlocking {
    // 将1到10转换为flow 过滤其中大于5的数字 并将这些数字形成一个映射 最后将其打印出来
    (1..10).asFlow().filter { it > 5 }.map { it -> myExecution(it) }// 这里中间函数map调用了挂起函数
        .collect { println(it) }
}

class HelloKotlin8 {
}

6 Flow 的中间运算符 transform

/**
 * Flow 的中间运算符 transform
 * transform内部可以随意处理每一个Flow元素 理论上 transform可以实现其他任意的中间操作符或者他们的组合
 *
 * Applies transform function to each value of the given flow.
 * The receiver of the transform is FlowCollector and thus transform is a flexible function that may transform emitted element, skip it or emit it multiple times.
 * This operator generalizes filter and map operators and can be used as a building block for other operators, for example:
 *
 * 将变换函数应用于给定Flow的每个值。
 * 变换的接收者是 FlowCollector,因此变换是一个灵活的函数,可以变换发射的元素,跳过它或多次发射它。
 * 此运算符概括了过滤器和映射运算符,可用作其他运算符的构建块
 */
private suspend fun myExecution(input: Int): String {
    delay(500)
    return "output: $input"
}

fun main() = runBlocking {
    (1..10).asFlow().transform { input ->
        if (input > 5){
            // transform可以让flow多次发射值 collect都能收集到
            emit("my input: $input")
            emit(myExecution(input))
            emit("hello")
        }
        // input为输入  emit的内容为输出
    }.collect { println(it) }
}
/*
输出
my input: 6
output: 6
hello
my input: 7
output: 7
hello
my input: 8
output: 8
hello
my input: 9
output: 9
hello
my input: 10
output: 10
hello

Process finished with exit code 0

 */
class HelloKotlin9 {
}

7 Flow 的中间运算符 take

/**
 * Flow 的中间运算符 take
 * 它本质是使用Kotlin的异常来实现只取出限定数量的数据‘
 *
 * 限定处理发射数量的中间操作符 当超出限制数量 会抛出异常
 * Flow.take方法可以限制指定数量的flow中的发射的元素
 *
 * Returns a flow that contains first count elements.
 * When count elements are consumed, the original flow is cancelled.
 * Throws IllegalArgumentException if count is not positive.
 * 返回包含第一个 count 元素的流。
 * 当 count 元素被消耗时,原始流被取消。
 * 如果 count 不是正数,则抛出 IllegalArgumentException。
 */
private fun myNumbers(): Flow = flow {
    try {
        emit(1)
        emit(2)
        println("middle")
        emit(3)
        emit(4)
    } catch (ex: Exception) {
        println(ex)
    } finally {
        println("finally")
    }
}

fun main() = runBlocking {
    myNumbers().take(2).collect {
        println(it)
    }
}
/*
输出
1
2
kotlinx.coroutines.flow.internal.AbortFlowException: Flow was aborted, no more elements needed
finally

Process finished with exit code 0
 */

class HelloKotlin10 {
}

8 Flow的终端操作符(Terminal Operation)

/**
 * Flow的终端操作符(Terminal Operation)
 * Flow的终止操作符都是挂起函数 终止操作才会真正开始流的收集
 *
 * 主要终端操作符
 * 1 除了collect 其他终端操作符 toList toSet
 * 2 只获取第一个元素
 * 3 reduce 将一系列值合成一个单个的值
 * Accumulates value starting with the first element and applying operation to current accumulator value and each element.
 * Throws NoSuchElementException if flow was empty.
 * 从第一个元素开始累加值,并将操作应用于当前累加器值和每个元素。
 * 如果流为空,则抛出 NoSuchElementException。
 */

fun main() = runBlocking {
    val result = (1..4).asFlow()
        .reduce { a, b -> a+b }
    println(result)

    val result1 = (1..4).asFlow()
        .toList()
    println(result1)

    val result2 = (1..4).asFlow()
        .toSet()
    println(result2)
}
class HelloKotlin11 {
}

9 Flow 默认是顺序执行的

/**
 * Flow 默认是顺序执行的
 * 对于Flow的收集来说,它是运行在调用了终止操作符的那个协程上。默认情况下,它不会启动新的协程。
 * 每个emit的元素值都会由所有的中间操作进行处理,最后再由终止操作进行处理。本质上就是每一个元素
 * 依次执行 中间操作 终端操作,这样一个一个顺序执行
 */

fun main() = runBlocking {
    (1..10).asFlow().filter {// 有的元素在第一步中就被过滤了
        println("第一步 filter: $it")
        it % 2 == 0
    }.map {
        println("第二步 map: $it")
        "Hello: $it"
    }.collect {
        println("第三步 final: $it")
        println("=============")
    }
}

class HelloKotlin12 {
}

10 Flow Context

/**
 * Flow Context(Flow 上下文)
 * Flow的收集总是发生在调用协程的上下文中,这个特性叫做上下文保留(Context Preservation)
 * 注意加上-Dkotlinx.coroutines.debug
 */
private fun log(logMessage: String) = println("[${Thread.currentThread().name}] $logMessage")

private fun myMethod(): Flow = flow {
    log("start")
    for (i in 1..4) {
        emit(i)
    }
}

fun main() = runBlocking {
    // 例如下面的Flow的收集操作发生在runBlocking的上下文 即主线程中
    myMethod().collect {
        log("Collected: $it")
    }
}

/*
输出

[main @coroutine#1] start
[main @coroutine#1] Collected: 1
[main @coroutine#1] Collected: 2
[main @coroutine#1] Collected: 3
[main @coroutine#1] Collected: 4
 */

class HelloKotlin13 {
}

11 flowOn 切换Flow上下文

/**
 * 尝试切换Flow上下文
 */
private fun myMethod(): Flow = flow {
    // 尝试将Flow的发射上下文更改
    withContext(Dispatchers.Default){
        for (i in 1..4) {
            emit(i)
        }
    }
}

fun main() = runBlocking {
    myMethod().collect {
        println(it)
    }
}
/*
输出

Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
		Flow was collected in [BlockingCoroutine{Active}@76aa9e23, BlockingEventLoop@5fcb12cd],
		but emission happened in [DispatchedCoroutine{Active}@1f1d9c3, Dispatchers.Default].
		Please refer to 'flow' documentation or use 'flowOn' instead
报错原因 违反了Flow的不变性
Flow收集发生在[BlockingCoroutine{Active}@76aa9e23, BlockingEventLoop@5fcb12cd]
Flow发射发生在[DispatchedCoroutine{Active}@1f1d9c3, Dispatchers.Default]
要实现这样的需求需要使用flowOn代替
 */
class HelloKotlin14 {
}





/**
 * flowOn 切换Flow上下文
 *
 * 借助flowOn,可以让Flow在发射元素所在的上下文与收集(终端操作符)所处的上下文是不同的。
 * 值得注意的是:flowOn运算符改变了Flow的顺序性。
 * 现在 收集操作发生在一个协程上,而发射操作发射在另外的协程
 * flowOn运算符本质上会改变上下文中的CoroutineDispatcher,并且为上游的flow创建另外一个协程
 * 上游可以简单理解为发生在当前操作的前面 不过 终端操作即使发生在中间操作之前,也不能称之为上游
 *
 * 注意加上-Dkotlinx.coroutines.debug
 */

private fun log(logMessage: String) = println("[${Thread.currentThread().name}] $logMessage")

private fun myMethod(): Flow = flow {
    for (i in 1..4) {
        Thread.sleep(100)
        log("emit: $i")
        emit(i)
    }
}.flowOn(Dispatchers.Default)// 对比14 的差异 在这里切换emit的上下文

fun main() = runBlocking {
    myMethod().collect {
        value -> log("Collected $value")
    }
}
/*
输出
[DefaultDispatcher-worker-1 @coroutine#2] emit: 1
[main @coroutine#1] Collected 1
[DefaultDispatcher-worker-1 @coroutine#2] emit: 2
[main @coroutine#1] Collected 2
[DefaultDispatcher-worker-1 @coroutine#2] emit: 3
[main @coroutine#1] Collected 3
[DefaultDispatcher-worker-1 @coroutine#2] emit: 4
[main @coroutine#1] Collected 4

可以看到Flow的发射与收集工作在不同的协程
 */

class HelloKotlin15 {

}

你可能感兴趣的:(Kotlin,kotlin,开发语言,android)