Flow: 是一種類似于序列的冷流,flow構(gòu)建器中的代碼直到流被收集的時(shí)候才運(yùn)行。
流的連續(xù)性:流的每次單獨(dú)收集都是按順序執(zhí)行的,除非使用特殊操作符。
從上游到下游每個(gè)過渡操作符都會處理每個(gè)發(fā)射出的值,然后再交給末端操作符。
flow構(gòu)建器創(chuàng)建一個(gè)函數(shù)
返回多個(gè)值,而且是異步的,不是一次性返回
(1)構(gòu)建流的三種方式
// flow構(gòu)建器創(chuàng)建一個(gè)函數(shù)
// 返回多個(gè)值,而且是異步的,不是一次性返回
suspend fun simpleFlow() = flow<Int> {
for (i in 1..3) {
delay(1000)
emit(i) // 發(fā)射,產(chǎn)生一個(gè)元素
}
}
runBlocking {
// Flow構(gòu)建方式1
simpleFlow().collect { value -> println(value) } // 收集元素
// Flow構(gòu)建方式2
(1..5).asFlow().filter {
it % 2 == 0
}.map {
println("Map $it")
}.onEach {
delay(1000)
}.collect {
println("Collect $it")
}
// Flow構(gòu)建方式3
flowOf("one", "two", "three").onEach { delay(1000) }.collect { values ->
println(values)
}
}
(2)流的上下文
// Flow上下文驗(yàn)證
(1..5).asFlow().filter {
println("當(dāng)前線程-filter:" + Thread.currentThread().name)
it % 2 == 0
}.map {
println("當(dāng)前線程-map:" + Thread.currentThread().name)
}.onEach {
delay(1000)
}.collect {
println("當(dāng)前線程-collect:" + Thread.currentThread().name)
println("Collect $it")
}
從打印結(jié)果上看,上游和下游都是在主線程。
但是,一般情況下,F(xiàn)low構(gòu)建之后的代碼塊中是耗時(shí)操作,所以不能放在主線程,解決方案是:在Flow構(gòu)建器后面添加 flowOn(Dispatchers.Default)
,改造后的代碼如下:
suspend fun simpleFlow() = flow<Int> {
for (i in 1..3) {
delay(1000)
emit(i) // 發(fā)射,產(chǎn)生一個(gè)元素
}
}.flowOn(Dispatchers.Default)
fun main() {
runBlocking {
// Flow構(gòu)建方式1
simpleFlow().collect { value -> println(value) } // 收集元素
// Flow構(gòu)建方式2
(1..5).asFlow().filter {
println("當(dāng)前線程-filter:" + Thread.currentThread().name)
it % 2 == 0
}.map {
println("當(dāng)前線程-map:" + Thread.currentThread().name)
}.onEach {
delay(1000)
}.flowOn(Dispatchers.Default).collect {
println("當(dāng)前線程-collect:" + Thread.currentThread().name)
println("Collect $it")
}
// Flow構(gòu)建方式3
flowOf("one", "two", "three").flowOn(Dispatchers.Default).onEach { delay(1000) }.collect { values ->
println(values)
}
}
}
(3)啟動(dòng)流
啟動(dòng)流:launchIn傳入?yún)f(xié)程作用域形參,使用launchIn替換collect我們可以在指定協(xié)程中啟動(dòng)流的收集
(1..5).asFlow().onEach {
delay(1000)
}.flowOn(Dispatchers.Default).launchIn(CoroutineScope(Dispatchers.IO)).join()
(1..5).asFlow().onEach {
delay(1000)
}.flowOn(Dispatchers.Default).launchIn(this).join()
(4)流的取消
使用 withTimeoutOrNull
方式取消:
suspend fun simpleFlow() = flow<Int> {
for (i in 1..3) {
delay(1000)
emit(i) // 發(fā)射,產(chǎn)生一個(gè)元素
}
}.flowOn(Dispatchers.Default)
fun main() {
runBlocking {
withTimeoutOrNull(2000) {
// Flow構(gòu)建方式1
simpleFlow().collect { value -> println(value) } // 收集元素
}
withTimeoutOrNull(2000) {
(1..5).asFlow().onEach {
delay(1000)
}.flowOn(Dispatchers.Default).collect {
println("Collect $it")
}
}
withTimeoutOrNull(2000) {
flowOf("one", "two", "three").flowOn(Dispatchers.Default).onEach { delay(1000) }.collect { values ->
println(values)
}
}
withTimeoutOrNull(2000) {
(1..5).asFlow().onEach {
delay(1000)
}.flowOn(Dispatchers.Default).launchIn(CoroutineScope(Dispatchers.IO)).join()
}
println("Done...")
}
}
另外,啟動(dòng)流還可以調(diào)用 cancelAndJoin
取消。
val job = (1..5).asFlow().onEach {
delay(1000)
}.flowOn(Dispatchers.Default).launchIn(CoroutineScope(Dispatchers.IO))
delay(1000)
job.cancelAndJoin()
(5)流的取消檢測
為方便起見,流構(gòu)建器對每個(gè)發(fā)射值執(zhí)行附加的ensureActive 檢測以進(jìn)行取消,這意味著從 flow{...} 發(fā)出的繁忙循環(huán)是可以取消的。
出于性能原因,大多數(shù)其他流操作不會自行執(zhí)行其他取消檢測,在協(xié)程處于繁忙循環(huán)的情況下,必須明確檢測是否取消。
通過cancellable操作符來執(zhí)行此操作。
suspend fun simpleFlow() = flow<Int> {
for (i in 1..5) {
delay(1000)
emit(i) // emit自帶檢測是否取消的能力
}
}.flowOn(Dispatchers.Default)
fun main() {
runBlocking {
// emit 自帶檢測是否取消的能力
simpleFlow().collect { value ->
if (value == 3) cancel()
}
// 如果沒有emit,需要使用 cancellable
(1..5).asFlow().cancellable().onEach {
delay(1000)
}.flowOn(Dispatchers.Default).collect { value ->
if (value == 3) cancel()
}
}
}
(6)背壓
背壓:水流受到與流動(dòng)方向一致的壓力。
生產(chǎn)者、消費(fèi)者模式,只要生產(chǎn)效率 > 消費(fèi)效率,那么就會產(chǎn)生背壓。
處理背壓的方式有:
- buffer(),并發(fā)運(yùn)行流中發(fā)射元素的代碼
- conflate(),合并發(fā)射項(xiàng),不對每個(gè)值進(jìn)行處理
- collectLatest(),取消并重新發(fā)射最后一個(gè)值
- 當(dāng)必須更改CoroutineDispatcher時(shí),flowOn操作符使用了相同的緩沖機(jī)制,但是buffer函數(shù)顯示地請求緩沖而
不改變執(zhí)行上下文
。
suspend fun simpleFlow() = flow<Int> {
for (i in 1..50) {
println("發(fā)送數(shù)據(jù):$i")
delay(100)
emit(i)
}
}
fun main() {
runBlocking {
val time = measureTimeMillis {
simpleFlow()
.collect { value ->
delay(300)
println("接收數(shù)據(jù):$value")
}
}
println("耗時(shí):$time")
}
}
以上代碼,發(fā)送數(shù)據(jù)和接收數(shù)據(jù)都是在同一個(gè)線程中并行執(zhí)行,如果存在耗時(shí)程序,將特別影響效率。
為了增加執(zhí)行效率,可以使用 buffer
設(shè)置緩存大小,從而起到加快執(zhí)行速率的效果。
val time = measureTimeMillis {
// 背壓
simpleFlow()
.buffer(10)
.collect { value ->
delay(300)
println("接收數(shù)據(jù):$value")
}
}
但是,從生產(chǎn)者/消費(fèi)者的設(shè)計(jì)思想的角度上考慮,發(fā)送數(shù)據(jù)最好放在子線程。
val time = measureTimeMillis {
// 背壓
simpleFlow()
.flowOn(Dispatchers.Default)
.collect { value ->
delay(300)
println("接收數(shù)據(jù):$value")
}
}
使用 flowOn
可以指定 Flow 的協(xié)程作用域,這樣可以將 并行
轉(zhuǎn)成 并發(fā)
,從而加快執(zhí)行效率。
runBlocking {
val time = measureTimeMillis {
// 背壓
simpleFlow()
.conflate()
.collect { value ->
delay(300)
println("接收數(shù)據(jù)==:$value")
}
}
println("耗時(shí):$time")
}
以上代碼使用 conflate
,中間一些元素不會處理,從而加快執(zhí)行效率。
val time = measureTimeMillis {
// 背壓
simpleFlow()
.collectLatest { value ->
delay(300)
println("接收數(shù)據(jù)==:$value")
}
以上代碼將 collect
改成 collectLatest
之后,只會處理最后一個(gè)值,從而加速執(zhí)行速度。
(7)轉(zhuǎn)換操作符
使用map轉(zhuǎn)換:
suspend fun simpleFlow() = flow<Int> {
for (i in 1..3) {
println(i)
emit(i)
}
}
fun main() {
runBlocking {
simpleFlow()
.map { value ->
"response $value"
}
.collect { value ->
println(value)
}
}
}
使用transform轉(zhuǎn)換,可以轉(zhuǎn)換成任意次、任意值的Flow:
suspend fun simpleFlow() = flow<Int> {
for (i in 1..3) {
println(i)
emit(i)
}
}
fun main() {
runBlocking {
simpleFlow()
.transform { request ->
emit("request $request")
emit("request $request")
}
.collect { value ->
println(value)
}
}
}
(8)限長操作符
take
是限長操作符,可以限制處理的數(shù)量:
suspend fun simpleFlow() = flow<Int> {
for (i in 1..3) {
println(i)
emit(i)
}
}
fun main() {
runBlocking {
simpleFlow()
.take(2)
.collect { value ->
println(value)
}
}
}
(9)末端操作符
末端操作符是在流上用于 啟動(dòng)流收集的掛起函數(shù)
。collect是最基礎(chǔ)的末端操作符,但是還有另外一些更加方便使用的末端操作符:
- 轉(zhuǎn)化為各種集合,例如:toList與toSet。
- 獲取第一個(gè)(first)值與確保流發(fā)射單個(gè)(single)值的操作符。
- 使用reduce與fold將流規(guī)約到單個(gè)值。
fun main() {
runBlocking {
val sum = simpleFlow()
.reduce { a, b ->
a + b
}
println(sum)
}
}
reduce 操作符可以將元素累加。
reduce的返回值類型必須和集合的元素類型相符。
suspend fun simpleFlow() = flow<Int> {
for (i in 1..3) {
emit(i)
}
}
fun main() {
runBlocking {
val newStr = simpleFlow()
.fold(StringBuilder()) { str: StringBuilder, a: Int ->
str.append(a).append(" ")
}
println(newStr)
}
}
而fold的返回值類型則不受約束。
(10)組合操作符
zip
操作符將兩個(gè)流合并。
runBlocking {
val nums1 = (1..3).asFlow()
val nums2 = flowOf("one", "two", "three")
nums1.zip(nums2) {a, b ->
"$a $b"
}.collect {value->
println(value)
}
}
(11)展平操作符
流表示異步接收的值序列,所以很容易遇到這種情況:每個(gè)值都會觸發(fā)對另一個(gè)值序列的請求,然而,由于流具有異步的性質(zhì),因此需要不同的展平模式,為此,存在一系列的流展平操作符:
- flatMapConcat:連接模式
- flatMapMerge:合并模式
- flatMapLatest: 最新展平模式
suspend fun requestFlow(i: Int) = flow<String> {
emit("request $i first")
delay(500)
emit("request $i second")
}
fun main() {
runBlocking {
val startTime = System.currentTimeMillis()
(1..3).asFlow()
.onEach { delay(100) }
.flatMapConcat {
requestFlow(it) // Flow的元素是Flow
}
.collect { value->
println("$value -- ${System.currentTimeMillis() - startTime}")
}
}
}
代碼中 flatMapConcat
可以換成 flatMapMerge
或者 flatMapLatest
。
三者的執(zhí)行結(jié)果是:
flatMapConcat :(requestFlow全部執(zhí)行完)
request 1 first -- 198
request 1 second -- 701
request 2 first -- 815
request 2 second -- 1319
request 3 first -- 1428
request 3 second -- 1932
flatMapMerge:(不需要等待requestFlow全部執(zhí)行完)
request 1 first -- 281
request 2 first -- 361
request 3 first -- 470
request 1 second -- 798
request 2 second -- 876
request 3 second -- 985
flatMapLatest:
request 1 first -- 250
request 2 first -- 376
request 3 first -- 485
request 3 second -- 1001
(12)流的異常處理
suspend fun requestFlow() = flow<Int> {
for (i in 1..3) {
emit(i)
throw RuntimeException("exception")
}
}.catch {e: Throwable ->
println("上游異常捕獲:" + e.message)
}
fun main() {
runBlocking {
try {
requestFlow()
.collect { value->
check(value < 2) // 檢查異常
println(value)
}
} catch (e: Throwable) {
println("下游異常捕獲:" + e.message)
}
}
}
check:檢查異常,一旦檢查到異常,程序crash。
下游通過 try...catch
捕獲異常,上游Flow自帶 catch
函數(shù)。
(13)流的完成
收集完成時(shí),使用 finally
,表示收集完成。
suspend fun requestFlow() = flow<Int> {
for (i in 1..3) {
emit(i)
}
}
fun main() {
runBlocking {
try {
requestFlow().collect { value-> println(value) }
} finally {
println("...完成...")
}
}
}
使用 onCompletion
也可以表示完成:
suspend fun requestFlow() = flow<Int> {
for (i in 1..3) {
emit(i)
throw RuntimeException("exception")
}
}.catch {exception->
println("catch -> exception:" + exception.message)
}
fun main() {
runBlocking {
requestFlow()
.onCompletion {exception ->
if (exception != null) { // 異常導(dǎo)致完成
println("finish -> exception:" + exception.message)
} else { // 正常結(jié)束
println("正常結(jié)束")
}
}
.collect { value-> println(value) }
}
}
onCompletion
可以拿到異常信息,但是不能捕獲異常。
(13)Flow實(shí)現(xiàn)多路復(fù)用
多數(shù)情況下,我們可以通過構(gòu)造合適的Flow來實(shí)現(xiàn)多路復(fù)用的效果。
data class User(val name: String)
data class Response<T>(val value: T, val isLocal: Boolean)
suspend fun CoroutineScope.getUserForLocal(name: String) = async {
delay(1000)
User(name)
}
suspend fun CoroutineScope.getUserFromRemote(name: String) = async {
delay(100)
User(name)
}
fun main() {
runBlocking {
val name = "guest"
// 兩個(gè)函數(shù)
listOf(::getUserForLocal, ::getUserFromRemote)
.map { function->
function.call(name)
}
.map { deferred ->
flow { emit(deferred.await()) }
}.merge().collect { user -> println(user) }
}
}
以上代碼用到了反射,需要引入依賴:
implementation 'org.jetbrains.kotlin:kotlin-reflect:1.0.6'
[完...]