如何在Kotlin中使用協(xié)程實現一個異步加載功能-創(chuàng)新互聯(lián)

如何在Kotlin中使用協(xié)程實現一個異步加載功能?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。

黃山ssl適用于網站、小程序/APP、API接口等需要進行數據傳輸應用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18982081108(備注:SSL證書合作)期待與您的合作!

使用Coroutine之前的初始配置

首先我們使用android studio 新建一個項目,并在新建項目的時候勾選【Include Kotlin support】,就像下邊這樣

如何在Kotlin中使用協(xié)程實現一個異步加載功能

項目創(chuàng)建成功后,我們需要在build.gradle文件中的android配置模塊下面增加如下的配置

kotlin {
 experimental {
 coroutines 'enable'
 }
}

然后在build.gradle文件中添加如下的依賴

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.20'
 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20'

完整的配置情況如下:

如何在Kotlin中使用協(xié)程實現一個異步加載功能

經過上邊的步驟Coroutine的配置就已經完成了。接下來我們就可以使用Coroutine了。

實現你的第一個Coroutine程序

現在我們來開始編寫我們的第一個Coroutine例子程序,這個程序的主要功能就是從手機媒體中加載一張圖片,并把它顯示在一個ImageView中。我們先來看看在未使用Coroutine之前使用同步的方式加載圖片的代碼如下:

val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
imageView.setImageBitmap(bitmap)

在上邊的代碼中我們從媒體讀取了一張圖片并把它轉化成Bitmap對象。因為這是一個IO操作,如果我們在UI主線程中調用這段代碼,將可能導致程序卡頓或產生ANR崩潰,所以我們需要在新開的線程中調用下邊的代碼

val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)

接著我們需要在UI線程中調用下邊的代碼來顯示加載的圖片

imageView.setImageBitmap(bitmap)

為了實現這一功能在傳統(tǒng)的android程序中我們需要使用Handler或AsyncTask將結果從非UI主線程發(fā)送到UI主線程進行顯示,我們需要編寫許多額外的代碼。并且這些代碼的可讀性也不是十分的友好。下邊我們來看看使用Kotlin的Coroutine來實現圖片的加載的代碼,如下:

val job = launch(Background) {
 val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,uri) 
 launch(UI) {
 imageView.setImageBitmap(bitmap)
 }
}

我們先忽略返回值job,我們稍后會進行介紹,在這兒我們關心的事情是launch函數和參數Background與UI。與之前使用同步的方式加載圖片相比唯一的不同就在于這兒我們調用了lauch函數。lauch()創(chuàng)建并啟動了一個協(xié)程,這兒的參數Background是一個CoroutineContext對象,確保這個協(xié)程運行在一個后臺線程,確保你的應用程序不會因耗時操作而阻塞和崩潰。你可以像下邊這樣定義一個CoroutineContext:

internal val Background = newFixedThreadPoolContext(2, "bg")

他將使用含有兩個線程的線程池來執(zhí)行協(xié)程里邊的操作。在第一個協(xié)程里邊我們又調用了launch(UI)創(chuàng)建并啟動了一個新的協(xié)程,這兒的UI并不是我們自己創(chuàng)建的,他是Kotlin在Android平臺里邊預定義的一個CoroutineContext,代表著在UI主線程中執(zhí)行協(xié)程里邊的操作。所以我們將更新程序界面的操作imageView.setImageBitmap(bitmap)放在了這個協(xié)程里。通過這兒的例子代碼你會發(fā)現在kotlin里邊使用協(xié)程來實現線程間的通信和切換非常的簡單,比RxJava還簡單。看上去就跟你寫同步的方式的代碼一樣。

取消協(xié)程

在上邊的例子中我們返回了一個Job類型的對象job。通過調用job.cancel()我們能夠取消一個協(xié)程。例如當我們退出當前Activity的時候,圖片還沒有加載完。這個時候我們就可以在onDestroy中調用job.cancel()來取消這個未完成的任務。這與我們使用Rxjava時調用dipose()或使用AsyncTask時調用cancel() 來取消未完成的操作的作用是一樣的。

LifecycleObserver

android 架構組件( Android Architecture Components )里邊引入了許多非常好的東西,比如:ViewModel, Room 和 LiveData以及Lifecycle API。給予我們一種非常安全簡便的方式監(jiān)聽Activity和Fragment的生命周期變化。接下來我們將使用他們來對之前加載圖片的例子進行改進,利用lifecycle對Activity生命周期進行監(jiān)聽并做出相應的處理(監(jiān)聽到Activity調用onDestroy()時自動取消后臺任務)。

我們定義如下的代碼來使用協(xié)程:

class CoroutineLifecycleListener(val deferred: Deferred<*>) : LifecycleObserver {
 @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
 fun cancelCoroutine() {
 if (!deferred.isCancelled) {
 deferred.cancel()
 }
 }
}

我們也創(chuàng)建了LifecycleOwner的一個擴展函數:

fun <T> LifecycleOwner.load(loader: () -> T): Deferred<T> {
 val deferred = async(context = Background, start = CoroutineStart.LAZY) {
 loader()
 }

 lifecycle.addObserver(CoroutineLifecycleListener(deferred))
 return deferred
}

在這個函數里邊有許多新的東西,即使看上去感到疑惑也不要緊,我們會一步一步的對其進行講解。我們在所有實現LifecycleOwner接口的類中擴展了一個load函數。也就是說當我們使用支持庫的時候我們可以在Activity或Fragment中直接調用這個load函數(支持庫里邊的AppCompatActivity和Fragment實現了LifecycleOwner接口)。為了能夠在這個函數里邊訪問lifecycle成員添加CoroutineLifecycleListener作為一個觀察者。

load()函數使用名為loader的lambda表達式作為參數(這個lambda表達式返回一個泛型類型T),在load()函數里邊我們調用了名叫async的函數,這個函數的作用也是用于創(chuàng)建一個協(xié)程。它使用Background作為上下文。注意第二個參數start = CoroutineStart.LAZY。它的意思是不會立即啟動一個協(xié)程。直到你顯示的請求他返回一個值的時候它才會啟動,稍后你會看到具體怎樣做。這個協(xié)程返回了一個Deferred<T>對象到調用者。它與我們之前提到的job對象是類似的,但是他可以攜帶一個延遲的值,類似于JavaScript 中的Promise或Java APIs中的Future<T> 。

接下來我們定義Deferred<T>類(前面我們在load函數中返回的類型)的一個擴展函數then() ,它也使用一個名叫block的lambda表達式作為參數。這個lambda表達式以T類型的對象作為參數。具體代碼如下:

infix fun <T> Deferred<T>.then(block: (T) -> Unit): Job {
 return launch(context = UI) {
 block(this@then.await())
 }
}

這個函數使用launch()創(chuàng)建了另外一個協(xié)程,這個新的協(xié)程將運行在程序的主線程中。我們在這個新的協(xié)程中調用了then函數中傳入的名叫block的lambda表達式并使用await()函數作為它的參數。await()是在主線程中調用的,但是他并不會阻塞主線程的執(zhí)行,它將掛起這個函數,主線程可以繼續(xù)做其他的事情。當值從其他協(xié)程中返回的時候,他將被喚醒并將值從Deferred傳遞到這個lambda中。掛起函數(Suspending functions)是協(xié)程中最主要的概念。

一旦Activity的onDestroy方法被調用的時候,我們在load()函數中添加的lifecycle觀察者將會取消第一個協(xié)程,也會使第二個協(xié)程被取消,避免block()被調用。

Kotlin Coroutine DSL

上邊我們定義了兩個擴展函數和一個用于取消協(xié)程的類,讓我們來看看如何使用它們,代碼如下:

load {
 MediaStore.Images.Media.getBitmap(contentResolver,uri)
} then {
 imageView.setImageBitmap(it)
}

在上邊的代碼中我們傳遞一個lambda到load()函數中,在這個lambda中調用了loadBitmapFromMediaStore()函數運行在一個后臺進程中。一旦loadBitmapFromMediaStore()函數返回Bitmap,load()函數將返回Deferred<Bitmap> 。擴展的函數then()是被infix修飾的,因此當Deferred<Bitmap>返回之后我們可以使用上面那種奇特的語法調用它。我們傳遞到then()中的lambda將接收到一個Bitmap對象。因此我們可以簡單的調用imageView.setImageBitmap(it)顯示這個Bitmap。

上邊的代碼可以被應用到任何別的需要使用異步調用并將值轉遞到主線程的操作中。和RxJava這種框架比起來Kotlin的協(xié)程可能沒有它那么強大。但是Kotlin的協(xié)程可讀性更強,也更簡單?,F在你可以安全的使用它來執(zhí)行你的異步操作了,再也不用擔心內存泄漏的發(fā)生了。如下是將上邊的代碼用于從網絡加載數據并顯示的例子:

load { restApi.fetchData(query) } then { adapter.display(it) }

看完上述內容,你們掌握如何在Kotlin中使用協(xié)程實現一個異步加載功能的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

本文名稱:如何在Kotlin中使用協(xié)程實現一個異步加載功能-創(chuàng)新互聯(lián)
文章分享:http://muchs.cn/article34/ddosse.html

成都網站建設公司_創(chuàng)新互聯(lián),為您提供App開發(fā)、手機網站建設、用戶體驗、定制開發(fā)Google、企業(yè)網站制作

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

成都網站建設