Bootstrap

kotlin--Flow的运用

Flow是kotlin提供的一个工具,使用协程封装成生产者-消费者模式,上流来负责生产,下流来接收消耗。
一、Flow的使用
1.Flow的创建

1.可以使用flow构建函数构建一个Flow类型返回值的函数
2.flow{}构建体中可以调用挂起函数,即上流
3.上流使用emit函数发射值
4.下流使用collect函数收集值

//上流函数
fun simpleFlow() = flow {
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}

fun main() {
    runBlocking {
        //下流接收数据
        simpleFlow().collect { value ->
            println(value)
        }

        println("finished")
    }
}

结果:
1
2
3
finished

2.Flow是冷流,所以collect是挂起函数,不是子协程,并且只有执行collect函数时,上流的代码才会被执行,所以在一个协程中多次调用collect,它们会按顺序执行
fun simpleFlow() = flow {
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}

fun main() {
    runBlocking {
        simpleFlow().collect { value ->
            println(value)
        }

        println("collect1 finished")

        simpleFlow().collect { value ->
            println(value)
        }

        println("collect2 finished")
    }
}

结果:
1
2
3
collect1 finished
1
2
3
collect2 finished

3.Flow的连续性

Flow也支持函数式编程,并且从上流到下流的每个过渡操作符都会处理发射值,最终流入下流

fun main() {
    runBlocking {
        flow {
            for (i in 1..5) {
                delay(100)
                emit(i)
            }
        }.filter {
            it % 2 == 0 //只取偶数
        }.map {
            "String $it"
        }.collect {
            println(it)
        }
    }
}

结果:
String 2
String 4

4.Flow构建器

除了使用flow函数外,还有两种方式
1.flowOf函数
2.使用.asFlow()扩展函数,可以将各种集合与序列转为流

fun main() {
    runBlocking {
        val startTime = System.currentTimeMillis()

        flowOf(3, 5, 7)
            .onEach { delay(100) }
            .collect {
                println("${System.currentTimeMillis() - startTime}ms $it")
            }

        (3..6).asFlow().collect { println(it) }
    }
}

结果:
131ms 3
239ms 5
350ms 7
3
4
5
6

5.collect为挂起函数,但是Flow也提供了flowOn函数方便我们指定上流是否使用子协程执行
fun main() {
    runBlocking {
        flow {
            println("flow :${Thread.currentThread().name}")
            for (i in 1..5) {
                delay(100)
                emit(i)
            }
        }.flowOn(Dispatchers.Default)
            .collect {
                println("collect:${Thread.currentThread().name} $it")
            }
    }
}

结果:
flow :DefaultDispatcher-worker-1
collect:main 1
collect:main 2
collect:main 3
collect:main 4
collect:main 5

下流还是会使用主协程的上下文

6.除了使用子协程执行上流外,我们还可以使用launchIn函数来让Flow使用全新的协程上下文
fun main() {
    runBlocking {
        flow {
            println("flow :${Thread.currentThread().name}")
            for (i in 1..5) {
                delay(100)
                emit(i)
            }
        }.flowOn(Dispatchers.Default)
            .onEach { println("collect:${Thread.currentThread().name} $it") }
            .launchIn(CoroutineScope(Dispatchers.IO))
            .join()//主线程等待这个协程执行结束
    }
}

结果:
flow :DefaultDispatcher-worker-1
collect:DefaultDispatcher-worker-1 1
collect:DefaultDispatcher-worker-1 2
collect:DefaultDispatcher-worker-1 3
collect:DefaultDispatcher-worker-1 4
collect:DefaultDispatcher-worker-1 5

7.Flow的取消

Flow的取消和协程的取消相同,流的收集是CPU密集型的,但是如果收集时有挂起函数,那么挂起函数可以抛出取消异常来中断执行
使用了新协程的情况,可以使用cancel:

fun main() {
    runBlocking {
        val flow = flow {
            println("flow :${Thread.currentThread().name}")
            for (i in 1..5) {
                delay(100)
                emit(i)
            }
        }.flowOn(Dispatchers.Default)
            .onEach { println("collect:${Thread.currentThread().name} $it") }
            .launchIn(CoroutineScope(Dispatchers.IO))

        delay(200)
        flow.cancel()
        flow.join()
    }
}

使用timeout:

fun main() {
    runBlocking {
        withTimeoutOrNull(300){
            flow {
                println("flow :${Thread.currentThread().name}")
                for (i in 1..5) {
                    delay(100)
                    emit(i)
                }
            }.flowOn(Dispatchers.Default)
                .collect { println("collect:${Thread.currentThread().name} $it") }
        }
        
        println("finished")
    }
}
8.Flow的取消检测

之前我们调用子协程的取消时,CPU密集型代码并不能结束运行,在不使用挂起函数的情况下,我们在子协程体中通过ensureActive函数来检测该协程是否被取消了
1.而Flow为了方便,Flow构建器会对每个发射值(emit函数)执行ensureActive函数来进行取消

fun main() {
    runBlocking {
        flow {
            for (i in 1..5) {
                emit(i)
            }
        }
            .collect {
                println("$it")
                if (it > 2)
                    cancel()
            }

        println("finished")
    }
}

结果:
1
2
3
Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job=BlockingCoroutine{Cancelled}@2833cc44

2.出于性能考虑,大多数其他流操作不会执行检测,此时我们可以使用cancellable函数来指定该Flow是可以取消的

fun main() {
    runBlocking {
        val flow = flowOf(1, 2, 3, 5)
            .cancellable()//不指定,那么将不执行取消检测
            .collect {
                println("$it")
                if (it > 2)
                    cancel()
            }

        println("finished")
    }
}

结果:
1
2
3
Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job=BlockingCoroutine{Cancelled}@7c29daf3

9.背压

上流每次发射耗时1s,下流接收耗时3s,那么它们总共会耗时多久

fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                delay(1000)
                emit(i)
            }
        }

        val time = measureTimeMillis {
            flow
                .collect {
                delay(3000)
                println("$it")
            }
        }

        println("time : $time ms")
    }
}

结果:
1
2
3
time : 12073 ms
可以看出,一般情况下,上下流执行是同步的

1.使用buff,来让上流不等待下流接收,而是发射到缓存区

fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                delay(1000)
                emit(i)
            }
        }

        val time = measureTimeMillis {
            flow.buffer(50)//指定缓存区大小为50个
                .collect {
                delay(3000)
                println("$it")
            }
        }

        println("time : $time ms")
    }
}

结果:
1
2
3
time : 10158 ms
时间是1s + 3s * 3

2.指定上流协程

fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                delay(1000)
                emit(i)
            }
        }

        val time = measureTimeMillis {
            flow.flowOn(Dispatchers.IO)
                .collect {
                    delay(3000)
                    println("$it")
                }
        }

        println("time : $time ms")
    }
}

结果和1.是一样的

3.有时我们不需要一个不漏的接收上流的元素时,可以使用conflate,下流来不及处理的会被丢弃掉

fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                delay(1000)
                emit(i)
            }
        }

        val time = measureTimeMillis {
            flow.conflate()
                .collect {
                    delay(3000)
                    println("$it")
                }
        }

        println("time : $time ms")
    }
}

结果:
1
3
time : 7124 ms

4.collectLast可以只接收上流发射的最后一个元素

fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                delay(1000)
                emit(i)
            }
        }

        val time = measureTimeMillis {
            flow
                .collectLatest {
                    delay(3000)
                    println("$it")
                }
        }

        println("time : $time ms")
    }
}

3
time : 6144 ms

二、操作符

上面我们也提到了Flow支持函数式编程,用法和之前学习的差不多

1.转换操作符

1.map函数

fun main() {
    runBlocking {
        flow {
            for (i in 1..3) {
                emit(i)
            }
        }.map {
            "String $it"
        }.collect {
            println(it)
        }
    }
}

结果:
String 1
String 2
String 3

2.transform函数,还可以将上流的一个变为多个发射出去

fun main() {
    runBlocking {
        flow {
            for (i in 1..3) {
                emit(i)
            }
        }.transform {
            emit("String1 $it")
            emit("String2 $it")
        }.collect {
            println(it)
        }
    }
}

结果:
String1 1
String2 1
String1 2
String2 2
String1 3
String2 3

2.限长操作符

take函数

fun main() {
    runBlocking {
        flow {
            for (i in 1..3) {
                emit(i)
            }
        }.take(2).collect {
            println(it)
        }
    }
}

结果:
1
2

3.末端操作符

末端操作符是用于启动流的挂起函数,collect是最基础的末端操作符,除此以外还有其他的
1.转化为各种集合,如:toList或toSet
2.获取第一个元素(first)与确保流只发射一个元素(single)
3.flod与reduce将流整合到一个值

flod函数

fun main() {
    runBlocking {
       val value =  flow {
            for (i in 1..3) {
                emit(i)
            }
        }.map {
            it * it
        }.fold(0) { acc, value ->
            acc + value
        }
        
        print(value)
    }
}

结果:
14

4.组合操作符

zip函数

fun main() {
    runBlocking {
        val flow1 = flow {
            for (i in 1..3) {
                emit(i)
            }
        }

        val flow2 = flowOf("one", "two", "three")

        flow1.zip(flow2) { a, b ->
            "$a -> $b"
        }.collect { value ->
            println(value)
        }
    }
}

结果:
1 -> one
2 -> two
3 -> three

5.展平操作符

类似于集合的集合,流里也有可能有流,那么这个时候我们就需要使用展平操作符了
1.flatMapConcat

fun main() {
    runBlocking {
        val startTime = System.currentTimeMillis()
        flow {
            for (i in 1..3) {
                emit(i)
            }
        }.flatMapConcat {
            flow {
                emit("first $it")
                delay(500)
                emit("second $it")
            }
        }.collect {
            println("${System.currentTimeMillis() - startTime}ms  $it")
        }
    }
}

结果:
52ms first 1
570ms second 1
571ms first 2
1074ms second 2
1074ms first 3
1577ms second 3

2.flatMapMerge
和flatMapConcat不同,flatMapConcat是按流函数体中顺序执行,而flatMapMerge中遇到发射函数时,会一次性执行上流的所有发射

fun main() {
    runBlocking {
        val startTime = System.currentTimeMillis()
        flow {
            for (i in 1..3) {
                emit(i)
            }
        }.flatMapMerge {
            flow {
                emit("first $it")
                delay(500)
                emit("second $it")
            }
        }.collect {
            println("${System.currentTimeMillis() - startTime}ms  $it")
        }
    }
}

结果:
130ms first 1
130ms first 2
131ms first 3
632ms second 1
632ms second 2
632ms second 3

3.flatMapLatest
flatMapLatest中遇到第二个发射函数时,只会发射上流最后一次的元素

fun main() {
    runBlocking {
        val startTime = System.currentTimeMillis()
        flow {
            for (i in 1..3) {
                emit(i)
            }
        }.flatMapLatest {
            flow {
                emit("first $it")
                delay(500)
                emit("second $it")
            }
        }.collect {
            println("${System.currentTimeMillis() - startTime}ms  $it")
        }
    }
}

结果:
298ms first 1
300ms first 2
301ms first 3
806ms second 3

三、Flow的异常处理

当运算符中的发射器或代码抛出异常,可以有两种方式处理
1.try catch
2.catch函数

1.try catch适用于收集时发生的异常
fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                emit(i)
            }
        }

        try {
            flow.collect {
                println(it)
                throw RuntimeException()
            }
        } catch (e: Exception) {
            print("caught: $e")
        }
    }
}
2.虽然上流也可以使用try catch,但是更推荐catch函数
fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                emit(i)
                throw RuntimeException()
            }
        }.catch { e ->
            print("caught1: $e")
        }.collect {
            println(it)
        }
    }
}
四、Flow的完成

有时候我们需要在Flow完成时,做一些其他事情,可以使用下面的方式

1.finally块

fun main() {
    runBlocking {
        try{
            val flow = flow {
                for (i in 1..3) {
                    emit(i)
                }
            }.collect {
                println(it)
            }
        }finally {
            println("done")            
        }
    }
}

2.onCompletion函数

fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                emit(i)
            }
        }.onCompletion {
            println("done")
        }.collect {
            println(it)
        }
    }
}
;