前言
在最近的三篇中,標題都是 ***** 看這一篇就夠了,而這篇關于Flow的,我認慫了,只能說 看這一篇 帶你入門~,因為我發現Flow牽扯的東西實在是太多了,就像RxJava別說兩篇 可能五篇也是說不完的,
為什么需要Flow
首先我們來回顧下Kotlin中我們如何使用掛起函式,我們在main方法中,呼叫掛起函式回傳一組資料,代碼如下所示:
suspend fun loadData(): List<Int> {
delay(1000)
return listOf(1, 2, 3)
}
fun main() {
runBlocking {
loadData().forEach { value -> println(value) }
}
}
運行main函式,結果為 1s后 輸出1 2 3
那么我們想一下,如果loadData中的資料集合,并不是一起回傳的,比如從網路中先獲取到了1 在獲取到了2 最后再獲取到了3 ,那么這樣如果我們仍然在回傳最后一個結果(其實也不知道)時一并回傳資料,會造成資源浪費并且用戶體驗不好,那么我們如何解決這個問題呢?
上面掛起函式的回傳型別是List型別,那么就必定只能一次性回傳資料,此時,Flow就出場了~
Flow的基礎使用
構建器
我們改寫loadData方法,回傳型別修改為Flow<Int> ,并構造一個flow,在flow中 每隔一秒,發送一個資料用來模擬延遲獲取值,代碼如下所示:
fun loadData() = flow {
for (i in 1..3) {
delay(1000)
emit(i)
}
}
fun main() {
runBlocking {
loadData1().collect {
println(it)
}
}
}
運行結果即是,每隔1秒鐘,列印出來一個數字,emit 方法用于發射值,collect方法是收集值,這里需要注意的是,我們可以看到 在main方法協程中,我們可以直接呼叫loadData的方法,這是因為flow構建塊中的代碼 就是一個suspend函式,這樣一來我們就實作了對資料的逐步加載,而不需要等待所有的資料回傳,
接下來我們在main方法中呼叫多次loadData方法而不呼叫collect,看會有什么現象,修改代碼如下所示:
fun loadData1() = flow {
println("進入加載資料的方法")
for (i in 1..3) {
delay(1000)
emit(i)
}
}
fun main() {
runBlocking {
println("第一次準備呼叫加載資料的方法")
var data = loadData1()
println("第二次準備呼叫加載資料的方法")
var data2 = loadData1()
data2.collect {
println(it)
}
}
}
然后我們運行main方法,列印結果如下所示:
第一次準備呼叫加載資料的方法
第二次準備呼叫加載資料的方法
進入加載資料的方法
1
2
3
Process finished with exit code 0
我們會發現,如果我們沒有呼叫flow的collect方法,其實不會進入flow的代碼塊中,也就是說 Flow中的代碼直到被collect呼叫的時候才會運行,否則會立即回傳,
Flow的取消
如果我們需要定時取消Flow中代碼塊的執行,只需要使用withTimeoutOrNull函式添加超時時間即可,比如上述方法我們是在三秒內回傳123,我們限定其在2500毫秒內執行完畢
fun main() {
runBlocking {
withTimeoutOrNull(2500){
loadData1().collect {
println(it)
}
}
}
}
我們運行main方法,則只有1 2 兩個數字進行了列印
1
2
Process finished with exit code 0
Flow的運算子
類似集合的函式是Api,Flow中也有許多運算子,這里我們簡單舉幾個例子:
map
使用map我們可以將最終結果映射為其他型別,代碼如下所示:
fun changeData(value: Int): String {
return "列印的結果是:${value}"
}
fun main() {
runBlocking {
loadData1().map {
changeData(it)
}.collect{
println(it)
}
}
}
我們通過map運算子將結果映射為字串的形式,運行main 列印結果如下所示:
列印的結果是:1
列印的結果是:2
列印的結果是:3
Process finished with exit code 0
filter運算子
通過filter 我們可以對結果集添加過濾條件,如下所示,我們僅列印出大于1的值
runBlocking {
loadData1().filter {
it > 1
}.collect {
println(it)
}
}
故 列印結果如下所示:
2
3
Process finished with exit code 0
所有的運算子都是可以一起使用的,并非只能單獨使用,
我們上面呼叫的collect是末端運算子,在Flow中除了collect之外 還有toList、reduce、fold等運算子,
toList運算子我們可以很明顯的知道意為轉換為list集合,而reduce 和 fold 則可將最終的值轉為單一的值,
fun main() {
runBlocking {
var data = loadData1().reduce { a, b ->
a + b
}
println(data)
}
}
如上代碼,我們將Flow的每個結果最終求和,列印結果如下所示:
6
Process finished with exit code 0
flowOn
Flow的代碼塊是執行在執行時的背景關系中,比如 我們不能通過在flow中指定執行緒來運行Flow代碼中的代碼,如下所示:
fun loadData1() = flow {
withContext(Dispatchers.Default){
for (i in 1..3) {
delay(1000)
emit(i)
}
}
}
fun main() {
runBlocking {
loadData1().collect { value -> println("Collected $value") }
}
}
此種運行方式,將會拋出例外
Exception in thread "main" java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used
at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.missing(MainDispatchers.kt:113)
... ...
那么我們如何指定Flow代碼塊中的背景關系呢,我們需要使用flowOn運算子,我們將Flow代碼塊中的代碼指定在IO執行緒中,代碼如下所示:
fun loadData1() = flow {
for (i in 1..3) {
delay(1000)
emit(i)
}
}.flowOn(Dispatchers.IO)
這樣我們就把Flow代碼塊中的事情放到了IO執行緒中,
buffer運算子
我們在Kotlin 協程 看這一篇就夠了 中曾了解過,協程可以提升并發請求的效率,而在Flow代碼塊中,每當有一個處理結果 我們就可以收到,但如果處理結果也是耗時操作,我們來看下需要多長時間來處理,我們在列印前間隔兩秒,并記錄開始和完成的時間,代碼如下所示:
var startTime: Long = 0
var endTime: Long = 0
fun loadData1() = flow {
startTime = System.currentTimeMillis() / 1000
for (i in 1..3) {
delay(1000)
emit(i)
}
}
fun main() {
runBlocking {
loadData1().collect { value ->
delay(2000)
println("$value")
}
endTime = System.currentTimeMillis() / 1000
println("處理時間:${endTime - startTime}s")
}
}
運行main方法得到結果如下:
1
2
3
處理時間:9s
Process finished with exit code 0
我們可以看到,處理三個資料,一共使用了9秒鐘的時間,
buffer運算子可以使發射和收集的代碼并發運行,從而提高效率,我們添加buffer代碼如下所示:
fun main() {
runBlocking {
loadData1().buffer().collect { value ->
delay(2000)
println("$value")
}
endTime = System.currentTimeMillis() / 1000
println("處理時間:${endTime - startTime}s")
}
}
再次運行main方法,結果如下所示:
1
2
3
處理時間:8s
Process finished with exit code 0
由此看出,時間較少了接近1s(/1000為近似值),不要小看這小小的1秒,運行在手機上還是相當重要的~
zip運算子
zip運算子,可以合并兩個flow,代碼如下所示:
fun loadData1() = flow {
for (i in 1..3) {
delay(1000)
emit("我是j:${i}")
}
}
fun loadData2() = flow {
for (i in 1..3) {
delay(1000)
emit("我是i:${i}")
}
}
fun main() {
runBlocking {
loadData2().zip(loadData1()){
a,b -> "$a,$b"
}.collect{
println(it)
}
}
}
運行結果如下所示:
我是i:1,我是j:1
我是i:2,我是j:2
我是i:3,我是j:3
Process finished with exit code 0
除此之外,Flow還有Combine、flatMapConcat、flatMapMerge等運算子,這里就不再一一講解了,
看了這么多,是否對Flow有所基本的了解了呢~
Flow 在 實際專案中的使用
未完待續,敬請期待,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/203119.html
標籤:其他
