前言
Kotlin 擁有函數式編程的能力,使用Kotlin開發,可以簡化開發代碼,層次清晰,利于閱讀。
然而Kotlin擁有操作符很多,其中就包括了flow。Kotlin Flow 如此受歡迎大部分歸功于其豐富、簡潔的操作符,巧妙使用Flow操作符可以大大簡化我們的程序結構,提升可讀性與可維護性。本篇文章列舉一些比較常用 Kotlin 操作符,通過關鍵原理與使用場景列子來講解Flow操作符。
1. collect接收操作符
用于數據接收,此操作符沒有返回對象,后面不可再添加操作符:
fun flowCollect() {
//流啟動viewModelScope.launch {flow {for (i in 1..3) {Log.d("TAG"," flowCollect $i")emit(i)delay(1000)}}.collect {Log.d("TAG"," collect $it")}//后面不可再接收其他操作符}}
2. launchIn操作符
流的啟動主要有兩種,一種是上面的作用域.launch啟動一個流,用collect操作符接收數據;
一種是launchIn操作符啟動流,官方不建議用這種,可能出于數據安全考慮,一般建議在onResume方法調用后啟動協程,因為怕數據接收了,但View還沒創建出來。但鏈式調用真的很好用,用onEach操作符接收數據。
flow {for (i in 1..3) {Log.d("TAG"," flowCollect $i")emit(i)delay(1000)}}.onCompletion {Log.d("TAG","onCompletion ")}.launchIn(viewModelScope)//在ViewModel中,直接launchIn在ViewModelScope作用域
3. onEach操作符
返回一個流,該流在上游流的每個值向下游發出之前調用給定的操作。也可以用來接收數據,與上面collect不同的是此操作符返回流,我們后面還可接其他操作符。上面的launchIn操作符啟動時,可用于接收數據。
fun flowOnEach() {flow {for (i in 1..3) {LogUtils.d("Emitting $i")emit(i)delay(1000)}}.onEach {LogUtils.d("onEach $it")}.onCompletion {LogUtils.d("onCompletion ")}.launchIn(viewModelScope)}
4. reduce操作符
reduce 操作符可以將所有數據累加(加減乘除)得到一個結果,如下所示:
fun testReduce(){val result = listOf(1, 2, 3).reduce { a, b ->a + b}print("list reduce result:$result")}
查看測試結果輸出如下:
注意??:
1: 如果 flow 中沒有數據,將拋出異常。如不希望拋異常,可使用 reduceOrNull 方法。
2: reduce 操作符不能變換數據類型。比如,Int 集合的結果不能轉換成 String 結果。
5. fold操作符
fold 和 reduce 很類似,但是 fold 可以變換數據類型
有時候,我們不需要一個結果值,而是需要繼續操 flow,可使用 runningFold :
flowOf(1, 2, 3).runningFold("a") { a, b ->a + b
}.collect {println(it)
}
查看輸出結果:
a
a1
a12
a123
同樣的,reduce 也有類似的方法 runningReduce:
flowOf(1, 2, 3).runningReduce { a, b ->a + b
}.collect {println(it)
}
查看輸出結果:
1
3
6
6. debounce操作符
debounce 需要傳遞一個毫秒值參數,功能是:只有達到指定時間后才發出數據,最后一個數據一定會發出。
例如,定義 1000 毫秒,也就是 1 秒,被觀察者發出數據,1秒后,觀察者收到數據,如果 1 秒內多次發出數據,則重置計算時間。
flow {emit(1)delay(500)emit(2)delay(550)emit(3)delay(1000)emit(4)delay(1010)
}.debounce(1000
).collect {println(it)
}
查看輸出結果,只有休眠1000和1010符合要求:
3
4
所以,rebounce 的應用場景是限流功能。
7. sample操作符
sample 和 debounce 很像,區別是:在規定時間內,只發送一個數據:
flow {repeat(4) {emit(it)delay(50)}
}.sample(100).collect {println(it)
}
輸出結果:
1
3
所以,sample 的應用場景是截流功能。
8. flatmapMerge操作符
簡單的說就是獲得兩個 flow 的乘積或全排列,合并并且平鋪,發出一個 flow。
flowOf(1, 3).flatMapMerge {flowOf("$it a", "$it b")
}.collect {println(it)
}
輸出結果:
1 a
1 b
3 a
3 b
注意??:flatmapMerge 還有一個特性,flatmapMerge 可以設置并發量,可以理解為 flatmapMerge 是線程安全的,而 flatmapConcat 不是線程安全的。會在下一個操作符里提及。
9. flatmapConcat操作符
舉個列子:
flowOf(1, 3).flatMapConcat {flowOf("a", "b", "c")
}.collect {println(it)
}
功能和 flatmapMerge 一致,不同的是 flatmapMerge 可以設置并發量,可以理解為 flatmapMerge 是線程安全的,而 flatmapConcat 不是線程安全的。
本質上,在 flatmapMerge 的并發參數設置為 1 時,和 flatmapConcat 基本一致,而并發參數大于 1 時,采用 channel 的方式發出數據,具體內容請參閱源碼。
10. buffer操作符
介紹 buffer 的時候,先要看這樣一段代碼:
flowOf("A", "B", "C", "D")
.onEach {println("1 $it")
}
.collect { println("2 $it") }
我們注意查看一下輸出結果:
1 A
2 A
1 B
2 B
1 C
2 C
1 D
2 D
如果我們加上 buffer 的代碼:
flowOf("A", "B", "C", "D")
.onEach {println("1 $it")
}
.buffer()
.collect { println("2 $it") }
在查看一下加上 buffer 的代碼的輸出結果:
1 A
1 B
1 C
1 D
2 A
2 B
2 C
2 D
對比倆次輸出結果,會發現輸出內容有所不同,buffer 操作符可以改變收發順序,像有一個容器作為緩沖似的,在容器滿了或結束時,下游開始接到數據,onEach 添加延遲,效果更明顯。
11 combine操作符
合并兩個 flow,長的一方會持續接受到短的一方的最后一個數據,直到結束:
flowOf(1, 3).combine(flowOf("a", "b", "c")
) { a, b -> b + a }
.collect {println(it)
}
查看輸出結果如下:
a1
b3
c3
12. zip操作符
也是合并兩個 flow,結果長度與短的 flow 一致,很像木桶原理。
fun testZip() {runBlocking {val time = measureTimeMillis {val flow1 = flow { emit("a") emit ("b") emit ("c") emit ("d") }val flow2 = flow { emit("1") emit ("2") } flow1 . zip (flow2) { sex, subject -> "$sex-->$subject" }.collect { println(it) }} println ("use time:$time")}}
查看輸出結果:
a-->1
b-->2
use time:71
通過輸出可以看出flow2先結束了,并且flow1沒發送完成。
zip原理簡單來說:
可以看出,zip的特點:
短的Flow結束,另一個Flow也結束。
13. flatMapLatest操作符
與 collectLatest 操作符類似,處理最新值,也有相對應的“最新”展平模式,在發出新流后立即取消先前流的收集。 這由 flatMapLatest 操作符來實現。
14. distinctUntilChanged操作符
和前一個數據不同,才能收到,和前一個數據相同,則會被過濾掉。
flowOf(1, 1, 2, 2, 3, 1).distinctUntilChanged().collect {println(it)
}
運行查看輸出結果:
1
2
3
1
總結
Kotlin 擁有函數式編程的能力,同時Kotlin擁有很多操作符,其中就包括了flow。合理使用Kotlin操作符可以使我們的代碼更加簡化,層次清晰,利于閱讀。因此,要靈活使用Kotlin操作符。
參考:
- 協程文檔
- 協程中文網
- 這一次,讓Kotlin Flow 操作符真正好用起來
- Kotlin 協程Flow主要操作符(一)
- Kotlin 協程Flow主要操作符(二)