上一篇文章,我介紹了Kotlin協程的創建,使用,協作等內容,本篇將引入更多的使用場景,繼續帶你走進協程世界,
使用協程處理異步資料流
常用編程語言都會內置對同一型別不同物件的資料集表示,我們通常稱之為容器類,不同的容器類適用于不同的使用場景,Kotlin的Flow
就是在異步計算的需求下引入的,用于表示異步的資料流,
Flow
“問渠哪得清如許,為有源頭活水來”,異步資料流的基本就是以某種方式獲得異步資料,Kotlin提供了多種種方式,比較常用的就是Kotlin協程包的asFlow
擴展和flow
構造器,前者是對普通資料集的Flow
化封裝,沒有更多可言,我們著重來看后者,
flow
構造器的主要目標就是產生一個異步資料流,它是一個泛型函式,引數是一個掛起函式,并且是FlowCollector
是擴展函式,這個介面只有一個emit
方法,就是為創建的Flow
提供異步計算的資料的,因為它是掛起函式,所以我們能在里面使用其他掛起函式計算異步值,然后通過emit
方法將值發送出去,如此反復就能為下游操作提供源源不斷的資料流了,
事情還沒完,上面的步驟我們只是規定了創建資料的方式,并沒有真正執行,也就是建好了道路,但是還沒有車上路,那么,怎樣才能讓車在路上跑呢,查看Flow
的介面會發現,它提供了collect
方法來處理資料,collect
接收一個掛起函式作為處理邏輯,但是同時,collect
方法本身也是掛起函式,所以,這個方法只能在掛起函式中運行,有了這些知識,我們就可以寫出最簡單的異步資料流了,
1uspend fun compute():Int{
delay(123)
return 1024
}
viewModelScope.launch {
val flow=flow<Int> {
emit(9527)
emit(compute())
delay(256)
emit(256)
}
flow.collect {
println(it)
}
}
在flow
構造器里面隨意做各種操作,只要在必要的時候傳遞結果就行了,但是需要注意的是,emit
方法只能運行在同一個協程里,乍一看,這樣分開寫和寫在一起并沒有本質上的差別,但Flow
還能做到更多,
該給Flow換個作業環境了
上一節,我們那個簡單的示例,假如把構造器里面的資料獲取方法換成網路請求,應用就歇菜了,因為它們都是運行在主執行緒里面的,那么這個時候,看過上一篇文章的小伙伴馬上就會反應過來,用withContext
方法在構造器里面切換執行緒就行了哇,思路是很對,因為Flow
的默認配置就是構造器和collect
方法作業在同一執行緒,既然現在主執行緒不讓運行,那就把構造器的執行緒切換一下就行了唄,然后事實并不是這樣,這樣寫出來的代碼根本無法運行,因為官方提供了唯一的flowOn
方法來切換構造器的執行執行緒,使用也很簡單,就是對創建好的Flow
物件配置一次flowOn
方法就行了,
val flow=["1.jpg","2.jpg"].asFlow()
flow.map { decode(it) }
.flowOn(Dispatchers.IO)
viewModelScope.launch {
flow.collect{
adapter.add(it)
}
有些中間處理邏輯
熟悉RxJava的小伙伴可能有疑問了,這些操作RxJava也能完成,甚至還有更多的運算子來支持中間狀態的處理,那么異步資料流能做到這些嗎,毫無疑問,它可以,普通的資料集有map
,filter
等操作方法,對于異步資料流來說,這些方法同樣適用,而且這些方法引數都是掛起函式,都可以執行異步操作,而且它還有個更靈活的transform
方法,這個方法可以定制自己的運算子,實作更靈活的資料操作,
當然,上面那些運算子都只能實作單一異步流的操作,對于多資料流的支持,它也同樣不在話下,zip
可以將兩個兩個資料源兩兩合并起來,合成的資料流長度為兩個資料流中最短的那個資料流的長度,combine
則與zip
不同,它會將兩個資料流最近的發送資料作為輸入,也就是說,假如一塊一慢的兩個資料源,慢的資料源的元素可能會被多次取到,從而最終的資料流比最短的那個都長,
val flow = flowOf(1, 2).delayEach(10)
val flow2 = flowOf("a", "b", "c").delayEach(15)
flow.combine(flow2) { i, s -> i.toString() + s }.collect {
println(it) // Will print "1a 2a 2b 2c"
}
結束狀態跟蹤
上一節提到,由于資料源和處理邏輯不在同一個地方,所以很難確定最終的資料流大小,進而不知道資料流什么時候處理結束,而且中間操作也可能會改變資料流的大小,由此就更加難以確定資料處理結束的時機了,但是我們有的時候卻需要在資料處理完成后做一些操作,該怎么辦呢?這個時候當然是該onCompletion
方法上場了,這個方法有一個可為空的Throwable
型別引數,很顯然,這可以同時指示兩種處理結果,成功或者失敗,失敗就會將例外物件傳遞進來,
多個協程共同作業
很多時候,避免不了讓多個協程共同作業,對于回傳單個值的協程,上一篇我們也提到過了,可以傳遞async
構造器的回傳物件Deferred
,但是局限性就是這個物件只能傳遞一個值,針對多值傳遞的情況,Kotlin提供了Channel
的解決方法,Channel
類似于阻塞佇列,資料通過send
方法發送出去,在另外的地方使用receive
方法接收,通過這種方法,我們可以極大提供協程的作業效率,利用它就可以輕松實作生產者和消費者模型,
val chanel=Channel<Int>()
viewModelScope.launch(Dispatchers.IO) {
for (i in 1..5){
delay(1000)
chanel.send(i)
}
}
viewModelScope.launch {
for (i in chanel){
println("Handle ${i}")
}
}
當然,這只是最簡單的用法,還可以加入更多的生產者,或者不再需要資料時取消,甚至還有專門的product
構造器,直接獲得回傳多個值的協程物件,
總結
Kotlin協程有很多有用的API,這些API覆寫了大部分異步使用的場景,所以在使用協程的時候,我們首先需要明確使用場景,再根據使用場景確定使用哪一套API,這可以使我們避免陷入API恐懼癥,為此,我根據這兩篇文章的內容,整理出了一份情景表格,實際開發中可以參照使用,
Kotlin協程構造器
API | 使用場景 |
---|---|
launch | 執行耗時操作,不需要回傳值 |
async | 需要獲取耗時操作的單個回傳值 |
produce | 需要獲取耗時操作的多個回傳值 |
Kotlin協程協同工具
API | 使用場景 |
---|---|
Flow | 操作異步資料流 |
Channel | 協程間通信 |
青山不改,綠水長流,咱們下期見!
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/555428.html
標籤:其他
上一篇:【有獎調研】HarmonyOS新物種,鴻蒙流量新陣地——元服務邀你來答題!
下一篇:返回列表