您好,登錄后才能下訂單哦!
如何在Kotlin中使用協程實現一個異步加載功能?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
使用Coroutine之前的初始配置
首先我們使用android studio 新建一個項目,并在新建項目的時候勾選【Include Kotlin support】,就像下邊這樣
項目創建成功后,我們需要在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'
完整的配置情況如下:
經過上邊的步驟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)
為了實現這一功能在傳統的android程序中我們需要使用Handler或AsyncTask將結果從非UI主線程發送到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()創建并啟動了一個協程,這兒的參數Background是一個CoroutineContext對象,確保這個協程運行在一個后臺線程,確保你的應用程序不會因耗時操作而阻塞和崩潰。你可以像下邊這樣定義一個CoroutineContext:
internal val Background = newFixedThreadPoolContext(2, "bg")
他將使用含有兩個線程的線程池來執行協程里邊的操作。在第一個協程里邊我們又調用了launch(UI)創建并啟動了一個新的協程,這兒的UI并不是我們自己創建的,他是Kotlin在Android平臺里邊預定義的一個CoroutineContext,代表著在UI主線程中執行協程里邊的操作。所以我們將更新程序界面的操作imageView.setImageBitmap(bitmap)
放在了這個協程里。通過這兒的例子代碼你會發現在kotlin里邊使用協程來實現線程間的通信和切換非常的簡單,比RxJava還簡單。看上去就跟你寫同步的方式的代碼一樣。
取消協程
在上邊的例子中我們返回了一個Job類型的對象job。通過調用job.cancel()我們能夠取消一個協程。例如當我們退出當前Activity的時候,圖片還沒有加載完。這個時候我們就可以在onDestroy中調用job.cancel()來取消這個未完成的任務。這與我們使用Rxjava時調用dipose()或使用AsyncTask時調用cancel() 來取消未完成的操作的作用是一樣的。
LifecycleObserver
android 架構組件( Android Architecture Components )里邊引入了許多非常好的東西,比如:ViewModel, Room 和 LiveData以及Lifecycle API。給予我們一種非常安全簡便的方式監聽Activity和Fragment的生命周期變化。接下來我們將使用他們來對之前加載圖片的例子進行改進,利用lifecycle對Activity生命周期進行監聽并做出相應的處理(監聽到Activity調用onDestroy()時自動取消后臺任務)。
我們定義如下的代碼來使用協程:
class CoroutineLifecycleListener(val deferred: Deferred<*>) : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun cancelCoroutine() { if (!deferred.isCancelled) { deferred.cancel() } } }
我們也創建了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的函數,這個函數的作用也是用于創建一個協程。它使用Background作為上下文。注意第二個參數start = CoroutineStart.LAZY。它的意思是不會立即啟動一個協程。直到你顯示的請求他返回一個值的時候它才會啟動,稍后你會看到具體怎樣做。這個協程返回了一個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()
創建了另外一個協程,這個新的協程將運行在程序的主線程中。我們在這個新的協程中調用了then函數中傳入的名叫block的lambda表達式并使用await()函數作為它的參數。await()
是在主線程中調用的,但是他并不會阻塞主線程的執行,它將掛起這個函數,主線程可以繼續做其他的事情。當值從其他協程中返回的時候,他將被喚醒并將值從Deferred傳遞到這個lambda中。掛起函數(Suspending functions)是協程中最主要的概念。
一旦Activity的onDestroy方法被調用的時候,我們在load()函數中添加的lifecycle觀察者將會取消第一個協程,也會使第二個協程被取消,避免block()
被調用。
Kotlin Coroutine DSL
上邊我們定義了兩個擴展函數和一個用于取消協程的類,讓我們來看看如何使用它們,代碼如下:
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的協程可能沒有它那么強大。但是Kotlin的協程可讀性更強,也更簡單。現在你可以安全的使用它來執行你的異步操作了,再也不用擔心內存泄漏的發生了。如下是將上邊的代碼用于從網絡加載數據并顯示的例子:
load { restApi.fetchData(query) } then { adapter.display(it) }
看完上述內容,你們掌握如何在Kotlin中使用協程實現一個異步加載功能的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。