今天咱們來聊聊Kotlin Coroutine,若是你尚未了解過,那麼我要提早恭喜你,由於你將掌握一個新技能,對你的代碼方面的提高將是很好的助力。java
簡單的來講,Coroutine是一個併發的設計模式,你能經過它使用更簡潔的代碼來解決異步問題。android
例如,在Android方面它主要可以幫助你解決如下兩個問題:git
這些問題,在接下來的文章中我都會給出解決的示例。github
說到異步問題,咱們先來看下咱們常規的異步處理方式。首先第一種是最基本的callback方式。數據庫
callback的好處是使用起來簡單,但你在使用的過程當中可能會遇到以下情形設計模式
GatheringVoiceSettingRepository.getInstance().getGeneralSettings(RequestLanguage::class.java) .observe(this, { language -> convertResult(language, { enable -> // todo something }) })
這種在其中一個callback中回調另外一個callback回調,甚至更多的callback都是可能存在。這些狀況致使的問題是代碼間的嵌套層級太深,致使邏輯嵌套複雜,後續的維護成本也要提升,這不是咱們所要看到的。api
那麼有什麼方法可以解決呢?固然有,其中的一種解決方法就是我接下來要說的第二種方式。安全
對多嵌套回調,Rx系列在這方面處理的已經很是好了,例如RxJava。下面咱們來看一下RxJava的解決案例微信
disposable = createCall().map { // return RequestType }.subscribeWith(object : SMDefaultDisposableObserver<RequestType>{ override fun onNext(t: RequestType) { // todo something } })
RxJava豐富的操做符,再結合Observable與Subscribe可以很好的解決異步嵌套回調問題。可是它的使用成本就相對提升了,你要對它的操做符要很是瞭解,避免在使用過程當中濫用或者過分使用,這樣天然複雜度就提高了。網絡
那麼咱們渴望的解決方案是可以更加簡單、全面與健壯,而咱們今天的主題Coroutine就可以達到這種效果。
在Android裏,咱們都知道網絡請求應該放到子線程中,相應的回調處理通常都是在主線程,即ui線程。正常的寫法就很少說了,那麼使用Coroutine又該是怎麼樣的呢?請看下面代碼示例:
private suspend fun get(url: String) = withContext(Dispatchers.IO) { // to do network request url } private suspend fun fetch() { // 在Main中調用 val result = get("https://rousetime.com") // 在IO中調用 showToast(result) // 在Main中調用 }
若是fetch方法在主線程調用,那麼你會發現使用Coroutine來處理異步回調就像是在處理同步回調同樣,簡潔明瞭、行雲流水,同時再也沒有嵌套的邏輯了。
注意看方法,Coroutine爲了可以實現這種簡單的操做,增長了兩個操做來解決耗時任務,分別爲suspend與resume
解釋的有點生硬,簡單的來講就是suspend能夠將該任務掛起,使它暫時不在調用的線程中,以致於當前線程能夠繼續執行別的任務,一旦被掛起的任務已經執行完畢,那麼就會經過resume將其從新插入到當前線程中。
因此上面的示例展現的是,當get還在請求的時候,fetch方法將會被掛起,直到get結束,此時纔會插入到主線程中並返回結果。
一圖勝千言,我作了一張圖,但願能有所幫助。
另外須要注意的是,suspend方法只可以被其它的suspend方法調用或者被一個coroutine調用,例如launch。
另外一方面Coroutine使用Dispatchers來負責調度協調程序執行的線程,這一點與RxJava的schedules有點相似,但不一樣的是Coroutine必定要執行在Dispatchers調度中,由於Dispatchers將負責resume被suspend的任務。
Dispatchers提供三種模式切換,分別爲
再來看上面的示例
private suspend fun get(url: String) = withContext(Dispatchers.IO) { // to do network request url } private suspend fun fetch() { // 在Main中調用 val result = get("https://rousetime.com") // 在IO中調用 showToast(result) // 在Main中調用 }
爲了讓get操做運行在IO線程,咱們使用withContext方法,對該方法傳入Dispatchers.IO,使得它閉包下的任務都處於IO線程中,同時witchContext也是一個suspend函數。
上面提到suspend函數只能在相應的suspend中或者Coroutine中調用。那麼Coroutine又該如何建立呢?
有兩種方式,分別爲launch與async
仍是上面的例子,若是咱們須要執行fetch方法,可使用launch建立一個Coroutine
private fun excute() { CoroutineScope(Dispatchers.Main).launch { fetch() } }
另外一種async,由於它返回結果,若是要等全部async執行完畢,可使用await或者awaitAll
private suspend fun fetchAll() { coroutineScope { val deferredFirst = async { get("first") } val deferredSecond = async { get("second") } deferredFirst.await() deferredSecond.await() // val deferred = listOf( // async { get("first") }, // async { get("second") } // ) // deferred.awaitAll() } }
因此經過await或者awaitAll能夠保證全部async完成以後再進行resume調用。
若是你使用了Architecture Component,那麼你也能夠在其基礎上使用Coroutine,由於Kotlin Coroutine已經提供了相應的api而且定製了CoroutineScope。
若是你還不瞭解Architecture Component,強烈推薦你閱讀個人 Android Architecture Components 系列
在使用以前,須要更新architecture component的依賴版本,以下所示
object Versions { const val arch_version = "2.2.0-alpha01" const val arch_room_version = "2.1.0-rc01" } object Dependencies { val arch_lifecycle = "androidx.lifecycle:lifecycle-extensions:${Versions.arch_version}" val arch_viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.arch_version}" val arch_livedata = "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.arch_version}" val arch_runtime = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.arch_version}" val arch_room_runtime = "androidx.room:room-runtime:${Versions.arch_room_version}" val arch_room_compiler = "androidx.room:room-compiler:${Versions.arch_room_version}" val arch_room = "androidx.room:room-ktx:${Versions.arch_room_version}" }
在ViewModel中,爲了可以使用Coroutine提供了viewModelScope.launch,同時一旦ViewModel被清除,對應的Coroutine也會自動取消。
fun getAll() { viewModelScope.launch { val articleList = withContext(Dispatchers.IO) { articleDao.getAll() } adapter.clear() adapter.addAllData(articleList) } }
在IO線程經過articleDao從數據庫取數據,一旦數據返回,在主線程進行處理。若是在取數據的過程當中ViewModel已經清除了,那麼數據獲取也會中止,防止資源的浪費。
對於Lifecycle,提供了LifecycleScope,咱們能夠直接經過launch來建立Coroutine
private fun coroutine() { lifecycleScope.launch { delay(2000) showToast("coroutine first") delay(2000) showToast("coroutine second") } }
由於Lifecycle是能夠感知組件的生命週期的,因此一旦組件onDestroy了,相應的LifecycleScope.launch閉包中的調用也將取消中止。
lifecycleScope本質是Lifecycle.coroutineScope
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope get() = lifecycle.coroutineScope override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (lifecycle.currentState <= Lifecycle.State.DESTROYED) { lifecycle.removeObserver(this) coroutineContext.cancel() } }
它會在onStateChanged中監聽DESTROYED狀態,同時調用cancel取消Coroutine。
另外一方面,lifecycleScope還能夠根據Lifecycle不一樣的生命狀態進行suspend處理。例如對它的STARTED進行特殊處理
private fun coroutine() { lifecycleScope.launchWhenStarted { } lifecycleScope.launch { whenStarted { } delay(2000) showToast("coroutine first") delay(2000) showToast("coroutine second") } }
不論是直接調用launchWhenStarted仍是在launch中調用whenStarted都能達到一樣的效果。
LiveData中能夠直接使用liveData,在它的參數中會調用一個suspend函數,同時會返回LiveData對象
fun <T> liveData( context: CoroutineContext = EmptyCoroutineContext, timeoutInMs: Long = DEFAULT_TIMEOUT, @BuilderInference block: suspend LiveDataScope<T>.() -> Unit ): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
因此咱們能夠直接使用liveData來是實現Coroutine效果,咱們來看下面一段代碼
// Room @Query("SELECT * FROM article_model WHERE title = :title LIMIT 1") fun findByTitle(title: String): ArticleModel? // ViewModel fun findByTitle(title: String) = liveData(Dispatchers.IO) { MyApp.db.articleDao().findByTitle(title)?.let { emit(it) } } // Activity private fun checkArticle() { vm.findByTitle("Android Architecture Components Part1:Room").observe(this, Observer { }) }
經過title從數據庫中取數據,數據的獲取發生在IO線程,一旦數據返回,再經過emit方法將返回的數據發送出去。因此在View層,咱們能夠直接使用checkArticle中的方法來監聽數據的狀態。
另外一方面LiveData有它的active與inactive狀態,對於Coroutine也會進行相應的激活與取消。對於激活,若是它已經完成了或者非正常的取消,例如拋出CancelationException異常,此時將不會自動激活。
對於發送數據,還可使用emitSource,它與emit共同點是在發送新的數據以前都會將原數據清除,而不一樣點是,emitSource會返回一個DisposableHandle對象,以即可以調用它的dispose方法進行取消發送。
最後我使用Architecture Component與Coroutine寫了個簡單的Demo,你們能夠在Github中進行查看
源碼地址: https://github.com/idisfkj/an...
Android Architecture Components Part1:Room
Android Architecture Components Part2:LiveData
Android Architecture Components Part3:Lifecycle
Android Architecture Components Part4:ViewModel
掃描二維碼,關注微信公衆號,獲取獨家最新IT技術!