上一篇文章,我介紹了Kotlin協(xié)程的創(chuàng)建,使用,協(xié)作等內(nèi)容。本篇將引入更多的使用場景,繼續(xù)帶你走進(jìn)協(xié)程世界。
使用協(xié)程處理異步數(shù)據(jù)流
常用編程語言都會內(nèi)置對同一類型不同對象的數(shù)據(jù)集表示,我們通常稱之為容器類。不同的容器類適用于不同的使用場景。Kotlin的Flow
就是在異步計算的需求下引入的,用于表示異步的數(shù)據(jù)流。
Flow
“問渠哪得清如許,為有源頭活水來”,異步數(shù)據(jù)流的基本就是以某種方式獲得異步數(shù)據(jù)。Kotlin提供了多種種方式,比較常用的就是Kotlin協(xié)程包的asFlow
擴(kuò)展和flow
構(gòu)造器。前者是對普通數(shù)據(jù)集的Flow
化封裝,沒有更多可言,我們著重來看后者。flow
構(gòu)造器的主要目標(biāo)就是產(chǎn)生一個異步數(shù)據(jù)流,它是一個泛型函數(shù),參數(shù)是一個掛起函數(shù),并且是FlowCollector
是擴(kuò)展函數(shù)。這個接口只有一個emit
方法,就是為創(chuàng)建的Flow
提供異步計算的數(shù)據(jù)的,因為它是掛起函數(shù),所以我們能在里面使用其他掛起函數(shù)計算異步值,然后通過emit
方法將值發(fā)送出去,如此反復(fù)就能為下游操作提供源源不斷的數(shù)據(jù)流了。
事情還沒完,上面的步驟我們只是規(guī)定了創(chuàng)建數(shù)據(jù)的方式,并沒有真正執(zhí)行,也就是建好了道路,但是還沒有車上路。那么,怎樣才能讓車在路上跑呢,查看Flow
的接口會發(fā)現(xiàn),它提供了collect
方法來處理數(shù)據(jù)。collect
接收一個掛起函數(shù)作為處理邏輯,但是同時,collect
方法本身也是掛起函數(shù),所以,這個方法只能在掛起函數(shù)中運(yùn)行。有了這些知識,我們就可以寫出最簡單的異步數(shù)據(jù)流了。
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
構(gòu)造器里面隨意做各種操作,只要在必要的時候傳遞結(jié)果就行了,但是需要注意的是,emit
方法只能運(yùn)行在同一個協(xié)程里。乍一看,這樣分開寫和寫在一起并沒有本質(zhì)上的差別,但Flow
還能做到更多。
該給Flow換個工作環(huán)境了
上一節(jié),我們那個簡單的示例,假如把構(gòu)造器里面的數(shù)據(jù)獲取方法換成網(wǎng)絡(luò)請求,應(yīng)用就歇菜了。因為它們都是運(yùn)行在主線程里面的。那么這個時候,看過上一篇文章的小伙伴馬上就會反應(yīng)過來,用withContext
方法在構(gòu)造器里面切換線程就行了哇。思路是很對,因為Flow
的默認(rèn)配置就是構(gòu)造器和collect
方法工作在同一線程,既然現(xiàn)在主線程不讓運(yùn)行,那就把構(gòu)造器的線程切換一下就行了唄。然后事實并不是這樣,這樣寫出來的代碼根本無法運(yùn)行。因為官方提供了唯一的flowOn
方法來切換構(gòu)造器的執(zhí)行線程。使用也很簡單,就是對創(chuàng)建好的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也能完成,甚至還有更多的操作符來支持中間狀態(tài)的處理,那么異步數(shù)據(jù)流能做到這些嗎。毫無疑問,它可以。普通的數(shù)據(jù)集有map
,filter
等操作方法,對于異步數(shù)據(jù)流來說,這些方法同樣適用。而且這些方法參數(shù)都是掛起函數(shù),都可以執(zhí)行異步操作。而且它還有個更靈活的transform
方法,這個方法可以定制自己的操作符,實現(xiàn)更靈活的數(shù)據(jù)操作。
當(dāng)然,上面那些操作符都只能實現(xiàn)單一異步流的操作,對于多數(shù)據(jù)流的支持,它也同樣不在話下。zip
可以將兩個兩個數(shù)據(jù)源兩兩合并起來,合成的數(shù)據(jù)流長度為兩個數(shù)據(jù)流中最短的那個數(shù)據(jù)流的長度。combine
則與zip
不同,它會將兩個數(shù)據(jù)流最近的發(fā)送數(shù)據(jù)作為輸入,也就是說,假如一塊一慢的兩個數(shù)據(jù)源,慢的數(shù)據(jù)源的元素可能會被多次取到,從而最終的數(shù)據(jù)流比最短的那個都長。
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"
}
結(jié)束狀態(tài)跟蹤
上一節(jié)提到,由于數(shù)據(jù)源和處理邏輯不在同一個地方,所以很難確定最終的數(shù)據(jù)流大小,進(jìn)而不知道數(shù)據(jù)流什么時候處理結(jié)束。而且中間操作也可能會改變數(shù)據(jù)流的大小,由此就更加難以確定數(shù)據(jù)處理結(jié)束的時機(jī)了。但是我們有的時候卻需要在數(shù)據(jù)處理完成后做一些操作,該怎么辦呢?這個時候當(dāng)然是該onCompletion
方法上場了。這個方法有一個可為空的Throwable
類型參數(shù),很顯然,這可以同時指示兩種處理結(jié)果,成功或者失敗,失敗就會將異常對象傳遞進(jìn)來。
多個協(xié)程共同工作
很多時候,避免不了讓多個協(xié)程共同工作。對于返回單個值的協(xié)程,上一篇我們也提到過了,可以傳遞async
構(gòu)造器的返回對象Deferred
,但是局限性就是這個對象只能傳遞一個值。針對多值傳遞的情況,Kotlin提供了Channel
的解決方法。Channel
類似于阻塞隊列,數(shù)據(jù)通過send
方法發(fā)送出去,在另外的地方使用receive
方法接收。通過這種方法,我們可以極大提供協(xié)程的工作效率。利用它就可以輕松實現(xiàn)生產(chǎn)者和消費(fèi)者模型。
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}")
}
}
當(dāng)然,這只是最簡單的用法,還可以加入更多的生產(chǎn)者,或者不再需要數(shù)據(jù)時取消,甚至還有專門的product
構(gòu)造器,直接獲得返回多個值的協(xié)程對象。
總結(jié)
Kotlin協(xié)程有很多有用的API,這些API覆蓋了大部分異步使用的場景。所以在使用協(xié)程的時候,我們首先需要明確使用場景,再根據(jù)使用場景確定使用哪一套API,這可以使我們避免陷入API恐懼癥。為此,我根據(jù)這兩篇文章的內(nèi)容,整理出了一份情景表格,實際開發(fā)中可以參照使用。
Kotlin協(xié)程構(gòu)造器
API | 使用場景 |
---|---|
launch | 執(zhí)行耗時操作,不需要返回值 |
async | 需要獲取耗時操作的單個返回值 |
produce | 需要獲取耗時操作的多個返回值 |
Kotlin協(xié)程協(xié)同工具文章來源:http://www.zghlxwxcb.cn/news/detail-486639.html
API | 使用場景 |
---|---|
Flow | 操作異步數(shù)據(jù)流 |
Channel | 協(xié)程間通信 |
青山不改,綠水長流,咱們下期見!文章來源地址http://www.zghlxwxcb.cn/news/detail-486639.html
到了這里,關(guān)于Kotlin協(xié)程-從一到多的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!