您好,登錄后才能下訂單哦!
怎么在Kotlin中利用Coroutines執(zhí)行異步加載?針對這個問題,這篇文章詳細介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
引入Coroutines
//在application的build.gradle文件中的android節(jié)點添加如下的代碼 kotlin { experimental { coroutines 'enable' } } //添加下面兩行到依賴中 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.20" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20"
第一個Coroutines示例
通常我們加載一張圖片到ImageView中,異步的加載任務(wù)如下所示:
fun loadBitmapFromMediaStore(imageId: Int, imagesBaseUri: Uri): Bitmap { val uri = Uri.withAppendedPath(imagesBaseUri, imageId.toString()) return MediaStore.Images.Media.getBitmap(contentResolver, uri) }
這個方法必須在后臺線程中執(zhí)行,因為他屬于一個IO操作,這意味著我們有很多解決方案可以啟動后臺任務(wù),一旦該方法返回一個bitmap,我們需要立即顯示在Imageview中。
imageView.setImageBitmap(bitmap)
這行代碼必須在主線程執(zhí)行,否則會crash。
以上三行代碼如果寫到一起將會導致程序卡死或者是閃退,這都取決于合理的選擇線程。接下來我們看一下使用kotlin的Coroutines是如何解決這個問題的:
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)建和啟動一個Coroutine,Background參數(shù)CoroutineContext用來保證在后臺線程執(zhí)行,從而保證應(yīng)用程序不會卡死或者閃退,你可以聲明一個如下面所示的CoroutineContext。
internal val Background = newFixedThreadPoolContext(2, "bg")
這將創(chuàng)建一個新的上下文,并在執(zhí)行他的任務(wù)的時候使用兩個常規(guī)的線程。
接下來說launch(UI),這將觸發(fā)另一個coroutine,將執(zhí)行在Android
的主線程。
可取消
接下來的挑戰(zhàn)是處理跟Activity聲明周期相關(guān)的東西,當你在加載一個任務(wù),還沒有執(zhí)行完的時候離開了該Activity,以至于他在調(diào)用imageView.setImageBitmap(bitmap)
就會引起crash,所以我們在離開該activity之前就需要取消該任務(wù),這里就用到了launch()方法的返回值job,當activity調(diào)用onStop方法時,我們需要使用job來取消任務(wù)
job.cancel()
這就像你使用Rxjava時調(diào)用dispose和使用AsyncTask時調(diào)用cancel函數(shù)是一個道理。
LifecycleObserver
Android Architecture Components 給安卓開發(fā)者提供了特別多強大的庫,其中之一就是Lifecycle API.給我們提供了一個簡便的方法用來實時的監(jiān)聽activity和fragment的生命周期,我們定義一下代碼與coroutines一起使用。
class CoroutineLifecycleListener(val deferred: Deferred<*>) : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun cancelCoroutine() { if (!deferred.isCancelled) { deferred.cancel() } } }
我們創(chuàng)建一個LifecycleOwner的擴展函數(shù):
fun <T> LifecycleOwner.load(loader: () -> T): Deferred<T> { val deferred = async(context = Background, start = CoroutineStart.LAZY) { loader() } lifecycle.addObserver(CoroutineLifecycleListener(deferred)) return deferred }
這個方法中有太多新的東西,接下來一一解釋:
現(xiàn)在我們可以在一個activity或fragment中調(diào)用load()
,并從該函數(shù)中訪問生命周期成員,并將我們的CoroutineLifecycleListener添加為觀察者。
load方法需要一個loader作為參數(shù),返回一個通用類型T,在load方法中我,我們調(diào)用了另外一個Coroutine的創(chuàng)造者async()函數(shù),將會使用Background coroutine上下文在后臺線程中執(zhí)行任務(wù),注意這個方法還有另外一個參數(shù)start = CoroutineStart.LAZY,這意味著coroutine不會立即執(zhí)行,知道被調(diào)用為止。
coroutine接著會返回一個Defered<T>
對象給調(diào)用者,這與我們之前的Job類似,但它也可以攜帶一個延遲值,如常規(guī)Java API中的JavaScript Promise或Future <T>
,更好的是他有一個await方法.
接下來我們定義另外一個擴展函數(shù)then()
,這次是在Deferen<T>
上面定義,是我們上面的load方法返回的類型,它還將一個lambda作為參數(shù),命名為block,它將T類型的單個對象作為其參數(shù)。
infix fun <T> Deferred<T>.then(block: (T) -> Unit): Job { return launch(context = UI) { block(this@then.await()) } }
這個函數(shù)將使用launch()
函數(shù)創(chuàng)建另一個Coroutine ,這次在主線程上運行。傳遞給此Coroutine的lambda(命名塊)將完成的Deferred對象的值作為其參數(shù)。我們調(diào)用await()
來掛起這個Coroutine的執(zhí)行,直到Deferred對象返回一個值。
這里是coroutine變得如此令人印象深刻的地方。 await()
的調(diào)用是在主線程上完成的,但是不會阻塞該線程的進一步執(zhí)行。它將簡單地暫停該函數(shù)的執(zhí)行,直到它準備好,當它恢復(fù)并將延遲值傳遞給lambda時。coroutine暫停時,主線程可以繼續(xù)執(zhí)行其他的事情。await函數(shù)是coroutine中的一個核心概念,是什么創(chuàng)造了整個事物如此有魔力。
load()
函數(shù)中添加的生命周期觀察者將在我們的activity上調(diào)用onDestroy()
后取消第一個coroutine。這也會導致第二個coroutine被取消,阻止block()
被調(diào)用。
Kotlin Coroutine DSL
現(xiàn)在我們得到了兩個擴展函數(shù)和一個會處理coroutine被取消的類,讓我們來看看如何使用:
load { loadBitmapFromMediaStore(imageId, imagesBaseUri) } then { imageView.setImageBitmap(it) }
上面的代碼中,我們將lambda方法傳給load函數(shù),該函數(shù)調(diào)用loadBitmapFromMediaStore方法,該函數(shù)必須在后臺線程上執(zhí)行,直到該方法返回一個Bitmap,load方法的返回值是Deferred<Bitmap>
。
作為擴展函數(shù),then()
方法使用infix聲明,盡管load方法中返回的是Deferred<Bitmap>
,但是將會傳送給then方法一個bitmap返回值,所以我們可以直接在then方法中調(diào)用imageView.setImageBitmap(it)
。
上面的代碼可以用于任何需要在后臺線程上發(fā)生的異步調(diào)用,以及返回值應(yīng)該返回到主線程的地方,就像上面的例子。它不像RxJava那樣可以編寫多個調(diào)用,但它更容易閱讀,可能會涵蓋很多最常見的情況。現(xiàn)在你可以安全地做這樣的事情,而不必擔心在每個調(diào)用中造成context泄漏或處理線程;
load { restApi.fetchData(query) } then { adapter.display(it) }
關(guān)于怎么在Kotlin中利用Coroutines執(zhí)行異步加載問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。