Kotlin Coroutines執(zhí)行異步加載示例詳解
前言
Kotlin Coroutines是Kotlin推出的新的異步API。并不是解決所有問(wèn)題的最優(yōu)方案,但是希望在許多情況下它會(huì)使事情變得更容易一些。這里只簡(jiǎn)單的展示一下這個(gè)庫(kù)在安卓中的具體使用方案。下面話(huà)不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧。
引入Coroutines
//在application的build.gradle文件中的android節(jié)點(diǎn)添加如下的代碼
kotlin {
experimental {
coroutines 'enable'
}
}
//添加下面兩行到依賴(lài)中
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.20"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20"
第一個(gè)Coroutines示例
通常我們加載一張圖片到ImageView中,異步的加載任務(wù)如下所示:
fun loadBitmapFromMediaStore(imageId: Int,
imagesBaseUri: Uri): Bitmap {
val uri = Uri.withAppendedPath(imagesBaseUri, imageId.toString())
return MediaStore.Images.Media.getBitmap(contentResolver, uri)
}
這個(gè)方法必須在后臺(tái)線(xiàn)程中執(zhí)行,因?yàn)樗麑儆谝粋€(gè)IO操作,這意味著我們有很多解決方案可以啟動(dòng)后臺(tái)任務(wù),一旦該方法返回一個(gè)bitmap,我們需要立即顯示在Imageview中。
imageView.setImageBitmap(bitmap)
這行代碼必須在主線(xiàn)程執(zhí)行,否則會(huì)crash。
以上三行代碼如果寫(xiě)到一起將會(huì)導(dǎo)致程序卡死或者是閃退,這都取決于合理的選擇線(xiàn)程。接下來(lái)我們看一下使用kotlin的Coroutines是如何解決這個(gè)問(wèn)題的:
val job = launch(Background) {
val uri = Uri.withAppendedPath(imagesBaseUri, imageId.toString())
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,
launch(UI) {
imageView.setImageBitmap(bitmap)
}
}
這里最重要的是launch()和參數(shù)Background和UI,launch()表示創(chuàng)建和啟動(dòng)一個(gè)Coroutine,Background參數(shù)CoroutineContext用來(lái)保證在后臺(tái)線(xiàn)程執(zhí)行,從而保證應(yīng)用程序不會(huì)卡死或者閃退,你可以聲明一個(gè)如下面所示的CoroutineContext。
internal val Background = newFixedThreadPoolContext(2, "bg")
這將創(chuàng)建一個(gè)新的上下文,并在執(zhí)行他的任務(wù)的時(shí)候使用兩個(gè)常規(guī)的線(xiàn)程。
接下來(lái)說(shuō)launch(UI),這將觸發(fā)另一個(gè)coroutine,將執(zhí)行在A(yíng)ndroid
的主線(xiàn)程。
可取消
接下來(lái)的挑戰(zhàn)是處理跟Activity聲明周期相關(guān)的東西,當(dāng)你在加載一個(gè)任務(wù),還沒(méi)有執(zhí)行完的時(shí)候離開(kāi)了該Activity,以至于他在調(diào)用imageView.setImageBitmap(bitmap)就會(huì)引起crash,所以我們?cè)陔x開(kāi)該activity之前就需要取消該任務(wù),這里就用到了launch()方法的返回值job,當(dāng)activity調(diào)用onStop方法時(shí),我們需要使用job來(lái)取消任務(wù)
job.cancel()
這就像你使用Rxjava時(shí)調(diào)用dispose和使用AsyncTask時(shí)調(diào)用cancel函數(shù)是一個(gè)道理。
LifecycleObserver
Android Architecture Components 給安卓開(kāi)發(fā)者提供了特別多強(qiáng)大的庫(kù),其中之一就是Lifecycle API.給我們提供了一個(gè)簡(jiǎn)便的方法用來(lái)實(shí)時(shí)的監(jiān)聽(tīng)activity和fragment的生命周期,我們定義一下代碼與coroutines一起使用。
class CoroutineLifecycleListener(val deferred: Deferred<*>) : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun cancelCoroutine() {
if (!deferred.isCancelled) {
deferred.cancel()
}
}
}
我們創(chuàng)建一個(gè)LifecycleOwner的擴(kuò)展函數(shù):
fun <T> LifecycleOwner.load(loader: () -> T): Deferred<T> {
val deferred = async(context = Background,
start = CoroutineStart.LAZY) {
loader()
}
lifecycle.addObserver(CoroutineLifecycleListener(deferred))
return deferred
}
這個(gè)方法中有太多新的東西,接下來(lái)一一解釋?zhuān)?br />
現(xiàn)在我們可以在一個(gè)activity或fragment中調(diào)用load() ,并從該函數(shù)中訪(fǎng)問(wèn)生命周期成員,并將我們的CoroutineLifecycleListener添加為觀(guān)察者。
load方法需要一個(gè)loader作為參數(shù),返回一個(gè)通用類(lèi)型T,在load方法中我,我們調(diào)用了另外一個(gè)Coroutine的創(chuàng)造者async()函數(shù),將會(huì)使用Background coroutine上下文在后臺(tái)線(xiàn)程中執(zhí)行任務(wù),注意這個(gè)方法還有另外一個(gè)參數(shù)start = CoroutineStart.LAZY,這意味著coroutine不會(huì)立即執(zhí)行,知道被調(diào)用為止。
coroutine接著會(huì)返回一個(gè)Defered<T>對(duì)象給調(diào)用者,這與我們之前的Job類(lèi)似,但它也可以攜帶一個(gè)延遲值,如常規(guī)Java API中的JavaScript Promise或Future <T> ,更好的是他有一個(gè)await方法.
接下來(lái)我們定義另外一個(gè)擴(kuò)展函數(shù)then() ,這次是在Deferen<T>上面定義,是我們上面的load方法返回的類(lèi)型,它還將一個(gè)lambda作為參數(shù),命名為block,它將T類(lèi)型的單個(gè)對(duì)象作為其參數(shù)。
infix fun <T> Deferred<T>.then(block: (T) -> Unit): Job {
return launch(context = UI) {
block(this@then.await())
}
}
這個(gè)函數(shù)將使用launch()函數(shù)創(chuàng)建另一個(gè)Coroutine ,這次在主線(xiàn)程上運(yùn)行。傳遞給此Coroutine的lambda(命名塊)將完成的Deferred對(duì)象的值作為其參數(shù)。我們調(diào)用await()來(lái)掛起這個(gè)Coroutine的執(zhí)行,直到Deferred對(duì)象返回一個(gè)值。
這里是coroutine變得如此令人印象深刻的地方。 await()的調(diào)用是在主線(xiàn)程上完成的,但是不會(huì)阻塞該線(xiàn)程的進(jìn)一步執(zhí)行。它將簡(jiǎn)單地暫停該函數(shù)的執(zhí)行,直到它準(zhǔn)備好,當(dāng)它恢復(fù)并將延遲值傳遞給lambda時(shí)。coroutine暫停時(shí),主線(xiàn)程可以繼續(xù)執(zhí)行其他的事情。await函數(shù)是coroutine中的一個(gè)核心概念,是什么創(chuàng)造了整個(gè)事物如此有魔力。
load()函數(shù)中添加的生命周期觀(guān)察者將在我們的activity上調(diào)用onDestroy()后取消第一個(gè)coroutine。這也會(huì)導(dǎo)致第二個(gè)coroutine被取消,阻止block()被調(diào)用。
Kotlin Coroutine DSL
現(xiàn)在我們得到了兩個(gè)擴(kuò)展函數(shù)和一個(gè)會(huì)處理coroutine被取消的類(lèi),讓我們來(lái)看看如何使用:
load {
loadBitmapFromMediaStore(imageId, imagesBaseUri)
} then {
imageView.setImageBitmap(it)
}
上面的代碼中,我們將lambda方法傳給load函數(shù),該函數(shù)調(diào)用loadBitmapFromMediaStore方法,該函數(shù)必須在后臺(tái)線(xiàn)程上執(zhí)行,直到該方法返回一個(gè)Bitmap,load方法的返回值是Deferred<Bitmap> 。
作為擴(kuò)展函數(shù),then()方法使用infix聲明,盡管load方法中返回的是Deferred<Bitmap> ,但是將會(huì)傳送給then方法一個(gè)bitmap返回值,所以我們可以直接在then方法中調(diào)用imageView.setImageBitmap(it) 。
上面的代碼可以用于任何需要在后臺(tái)線(xiàn)程上發(fā)生的異步調(diào)用,以及返回值應(yīng)該返回到主線(xiàn)程的地方,就像上面的例子。它不像RxJava那樣可以編寫(xiě)多個(gè)調(diào)用,但它更容易閱讀,可能會(huì)涵蓋很多最常見(jiàn)的情況?,F(xiàn)在你可以安全地做這樣的事情,而不必?fù)?dān)心在每個(gè)調(diào)用中造成context泄漏或處理線(xiàn)程;
load { restApi.fetchData(query) } then { adapter.display(it) }
then()和load()方法只不過(guò)是這個(gè)新庫(kù)的冰山一角,但是我確實(shí)希望在未來(lái)的基于Kotlin的Android庫(kù)中出現(xiàn)類(lèi)似的東西,一旦coroutine版本達(dá)到穩(wěn)定版本。在此之前,您可以使用或修改上面的代碼,或者查看Anko Coroutines。我還在GitHub上發(fā)布了一個(gè)更完整的版本。 (https://github.com/ErikHellman/KotlinAsyncWithCoroutines (本地下載)).
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
使用Java注解模擬spring ioc容器過(guò)程解析
這篇文章主要介紹了使用Java注解模擬spring ioc容器過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
java.lang.NoClassDefFoundError錯(cuò)誤解決辦法
這篇文章主要介紹了java.lang.NoClassDefFoundError錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-06-06
idea注解參數(shù)換行時(shí)間日期格式設(shè)置方法
這篇文章主要介紹了idea注解參數(shù)換行時(shí)間日期格式設(shè)置方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
詳解springboot集成websocket的兩種實(shí)現(xiàn)方式
這篇文章主要介紹了springboot集成websocket的兩種實(shí)現(xiàn)方式,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Dwr3.0純注解(純Java Code配置)配置與應(yīng)用淺析二之前端調(diào)用后端
我們講到了后端純Java Code的Dwr3配置,完全去掉了dwr.xml配置文件,但是對(duì)于使用注解的類(lèi)卻沒(méi)有使用包掃描,而是在Servlet初始化參數(shù)的classes里面加入了我們的Service組件的聲明暴露,對(duì)于這個(gè)問(wèn)題需要后面我們?cè)偌?xì)細(xì)研究下這篇文章,主要分析介紹前端怎么直接調(diào)用后端2016-04-04
完美解決Get和Post請(qǐng)求中文亂碼的問(wèn)題
下面小編就為大家?guī)?lái)一篇完美解決Get和Post請(qǐng)求中文亂碼的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05

