摘要:例如,在方面它主要能夠幫助你解決以下兩個(gè)問(wèn)題在主線程中執(zhí)行耗時(shí)任務(wù)導(dǎo)致的主線程阻塞,從而使發(fā)生。提供主線程安全,同時(shí)對(duì)來(lái)自于主線程的網(wǎng)絡(luò)回調(diào)磁盤(pán)操提供保障。在線程通過(guò)從數(shù)據(jù)庫(kù)取數(shù)據(jù),一旦數(shù)據(jù)返回,在主線程進(jìn)行處理。
今天我們來(lái)聊聊Kotlin Coroutine,如果你還沒(méi)有了解過(guò),那么我要提前恭喜你,因?yàn)槟銓⒄莆找粋€(gè)新技能,對(duì)你的代碼方面的提升將是很好的助力。
What Coroutine簡(jiǎn)單的來(lái)說(shuō),Coroutine是一個(gè)并發(fā)的設(shè)計(jì)模式,你能通過(guò)它使用更簡(jiǎn)潔的代碼來(lái)解決異步問(wèn)題。
例如,在Android方面它主要能夠幫助你解決以下兩個(gè)問(wèn)題:
在主線程中執(zhí)行耗時(shí)任務(wù)導(dǎo)致的主線程阻塞,從而使App發(fā)生ANR。
提供主線程安全,同時(shí)對(duì)來(lái)自于主線程的網(wǎng)絡(luò)回調(diào)、磁盤(pán)操提供保障。
這些問(wèn)題,在接下來(lái)的文章中我都會(huì)給出解決的示例。
Callback說(shuō)到異步問(wèn)題,我們先來(lái)看下我們常規(guī)的異步處理方式。首先第一種是最基本的callback方式。
callback的好處是使用起來(lái)簡(jiǎn)單,但你在使用的過(guò)程中可能會(huì)遇到如下情形
GatheringVoiceSettingRepository.getInstance().getGeneralSettings(RequestLanguage::class.java) .observe(this, { language -> convertResult(language, { enable -> // todo something }) })
這種在其中一個(gè)callback中回調(diào)另一個(gè)callback回調(diào),甚至更多的callback都是可能存在。這些情況導(dǎo)致的問(wèn)題是代碼間的嵌套層級(jí)太深,導(dǎo)致邏輯嵌套復(fù)雜,后續(xù)的維護(hù)成本也要提高,這不是我們所要看到的。
那么有什么方法能夠解決呢?當(dāng)然有,其中的一種解決方法就是我接下來(lái)要說(shuō)的第二種方式。
Rx系列對(duì)多嵌套回調(diào),Rx系列在這方面處理的已經(jīng)非常好了,例如RxJava。下面我們來(lái)看一下RxJava的解決案例
disposable = createCall().map { // return RequestType }.subscribeWith(object : SMDefaultDisposableObserver{ override fun onNext(t: RequestType) { // todo something } })
RxJava豐富的操作符,再結(jié)合Observable與Subscribe能夠很好的解決異步嵌套回調(diào)問(wèn)題。但是它的使用成本就相對(duì)提高了,你要對(duì)它的操作符要非常了解,避免在使用過(guò)程中濫用或者過(guò)度使用,這樣自然復(fù)雜度就提升了。
那么我們渴望的解決方案是能夠更加簡(jiǎn)單、全面與健壯,而我們今天的主題Coroutine就能夠達(dá)到這種效果。
Coroutine在Kotlin中的基本要點(diǎn)在Android里,我們都知道網(wǎng)絡(luò)請(qǐng)求應(yīng)該放到子線程中,相應(yīng)的回調(diào)處理一般都是在主線程,即ui線程。正常的寫(xiě)法就不多說(shuō)了,那么使用Coroutine又該是怎么樣的呢?請(qǐng)看下面代碼示例:
private suspend fun get(url: String) = withContext(Dispatchers.IO) { // to do network request url } private suspend fun fetch() { // 在Main中調(diào)用 val result = get("https://rousetime.com") // 在IO中調(diào)用 showToast(result) // 在Main中調(diào)用 }
如果fetch方法在主線程調(diào)用,那么你會(huì)發(fā)現(xiàn)使用Coroutine來(lái)處理異步回調(diào)就像是在處理同步回調(diào)一樣,簡(jiǎn)潔明了、行云流水,同時(shí)再也沒(méi)有嵌套的邏輯了。
注意看方法,Coroutine為了能夠?qū)崿F(xiàn)這種簡(jiǎn)單的操作,增加了兩個(gè)操作來(lái)解決耗時(shí)任務(wù),分別為suspend與resume
suspend: 掛起當(dāng)前執(zhí)行的協(xié)同程序,并且保存此刻的所有本地變量
resume: 從它被掛起的位置繼續(xù)執(zhí)行,并且掛起時(shí)保存的數(shù)據(jù)也被還原
解釋的有點(diǎn)生硬,簡(jiǎn)單的來(lái)說(shuō)就是suspend可以將該任務(wù)掛起,使它暫時(shí)不在調(diào)用的線程中,以至于當(dāng)前線程可以繼續(xù)執(zhí)行別的任務(wù),一旦被掛起的任務(wù)已經(jīng)執(zhí)行完畢,那么就會(huì)通過(guò)resume將其重新插入到當(dāng)前線程中。
所以上面的示例展示的是,當(dāng)get還在請(qǐng)求的時(shí)候,fetch方法將會(huì)被掛起,直到get結(jié)束,此時(shí)才會(huì)插入到主線程中并返回結(jié)果。
一圖勝千言,我做了一張圖,希望能有所幫助。
另外需要注意的是,suspend方法只能夠被其它的suspend方法調(diào)用或者被一個(gè)coroutine調(diào)用,例如launch。
Dispatchers另一方面Coroutine使用Dispatchers來(lái)負(fù)責(zé)調(diào)度協(xié)調(diào)程序執(zhí)行的線程,這一點(diǎn)與RxJava的schedules有點(diǎn)類(lèi)似,但不同的是Coroutine一定要執(zhí)行在Dispatchers調(diào)度中,因?yàn)镈ispatchers將負(fù)責(zé)resume被suspend的任務(wù)。
Dispatchers提供三種模式切換,分別為
Dispatchers.Main: 使Coroutine運(yùn)行中主線程,以便UI操作
Dispatchers.IO: 使Coroutine運(yùn)行在IO線程,以便執(zhí)行網(wǎng)絡(luò)或者I/O操作
Dispatchers.Default: 在主線程之外提高對(duì)CPU的利用率,例如對(duì)list的排序或者JSON的解析。
再來(lái)看上面的示例
private suspend fun get(url: String) = withContext(Dispatchers.IO) { // to do network request url } private suspend fun fetch() { // 在Main中調(diào)用 val result = get("https://rousetime.com") // 在IO中調(diào)用 showToast(result) // 在Main中調(diào)用 }
為了讓get操作運(yùn)行在IO線程,我們使用withContext方法,對(duì)該方法傳入Dispatchers.IO,使得它閉包下的任務(wù)都處于IO線程中,同時(shí)witchContext也是一個(gè)suspend函數(shù)。
創(chuàng)建Coroutine上面提到suspend函數(shù)只能在相應(yīng)的suspend中或者Coroutine中調(diào)用。那么Coroutine又該如何創(chuàng)建呢?
有兩種方式,分別為launch與async
launch: 開(kāi)啟一個(gè)新的Coroutine,但不返回結(jié)果
async: 開(kāi)啟一個(gè)新的Coroutine,但返回結(jié)果
還是上面的例子,如果我們需要執(zhí)行fetch方法,可以使用launch創(chuàng)建一個(gè)Coroutine
private fun excute() { CoroutineScope(Dispatchers.Main).launch { fetch() } }
另一種async,因?yàn)樗祷亟Y(jié)果,如果要等所有async執(zhí)行完畢,可以使用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() } }
所以通過(guò)await或者awaitAll可以保證所有async完成之后再進(jìn)行resume調(diào)用。
Architecture Components如果你使用了Architecture Component,那么你也可以在其基礎(chǔ)上使用Coroutine,因?yàn)镵otlin Coroutine已經(jīng)提供了相應(yīng)的api并且定制了CoroutineScope。
如果你還不了解Architecture Component,強(qiáng)烈推薦你閱讀我的Android Architecture Components 系列
在使用之前,需要更新architecture component的依賴(lài)版本,如下所示
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}" }ViewModelScope
在ViewModel中,為了能夠使用Coroutine提供了viewModelScope.launch,同時(shí)一旦ViewModel被清除,對(duì)應(yīng)的Coroutine也會(huì)自動(dòng)取消。
fun getAll() { viewModelScope.launch { val articleList = withContext(Dispatchers.IO) { articleDao.getAll() } adapter.clear() adapter.addAllData(articleList) } }
在IO線程通過(guò)articleDao從數(shù)據(jù)庫(kù)取數(shù)據(jù),一旦數(shù)據(jù)返回,在主線程進(jìn)行處理。如果在取數(shù)據(jù)的過(guò)程中ViewModel已經(jīng)清除了,那么數(shù)據(jù)獲取也會(huì)停止,防止資源的浪費(fèi)。
LifecycleScope對(duì)于Lifecycle,提供了LifecycleScope,我們可以直接通過(guò)launch來(lái)創(chuàng)建Coroutine
private fun coroutine() { lifecycleScope.launch { delay(2000) showToast("coroutine first") delay(2000) showToast("coroutine second") } }
因?yàn)長(zhǎng)ifecycle是可以感知組件的生命周期的,所以一旦組件onDestroy了,相應(yīng)的LifecycleScope.launch閉包中的調(diào)用也將取消停止。
lifecycleScope本質(zhì)是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() } }
它會(huì)在onStateChanged中監(jiān)聽(tīng)DESTROYED狀態(tài),同時(shí)調(diào)用cancel取消Coroutine。
另一方面,lifecycleScope還可以根據(jù)Lifecycle不同的生命狀態(tài)進(jìn)行suspend處理。例如對(duì)它的STARTED進(jìn)行特殊處理
private fun coroutine() { lifecycleScope.launchWhenStarted { } lifecycleScope.launch { whenStarted { } delay(2000) showToast("coroutine first") delay(2000) showToast("coroutine second") } }
不管是直接調(diào)用launchWhenStarted還是在launch中調(diào)用whenStarted都能達(dá)到同樣的效果。
LiveDataLiveData中可以直接使用liveData,在它的參數(shù)中會(huì)調(diào)用一個(gè)suspend函數(shù),同時(shí)會(huì)返回LiveData對(duì)象
funliveData( context: CoroutineContext = EmptyCoroutineContext, timeoutInMs: Long = DEFAULT_TIMEOUT, @BuilderInference block: suspend LiveDataScope .() -> Unit ): LiveData = CoroutineLiveData(context, timeoutInMs, block)
所以我們可以直接使用liveData來(lái)是實(shí)現(xiàn)Coroutine效果,我們來(lái)看下面一段代碼
// 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 { }) }
通過(guò)title從數(shù)據(jù)庫(kù)中取數(shù)據(jù),數(shù)據(jù)的獲取發(fā)生在IO線程,一旦數(shù)據(jù)返回,再通過(guò)emit方法將返回的數(shù)據(jù)發(fā)送出去。所以在View層,我們可以直接使用checkArticle中的方法來(lái)監(jiān)聽(tīng)數(shù)據(jù)的狀態(tài)。
另一方面LiveData有它的active與inactive狀態(tài),對(duì)于Coroutine也會(huì)進(jìn)行相應(yīng)的激活與取消。對(duì)于激活,如果它已經(jīng)完成了或者非正常的取消,例如拋出CancelationException異常,此時(shí)將不會(huì)自動(dòng)激活。
對(duì)于發(fā)送數(shù)據(jù),還可以使用emitSource,它與emit共同點(diǎn)是在發(fā)送新的數(shù)據(jù)之前都會(huì)將原數(shù)據(jù)清除,而不同點(diǎn)是,emitSource會(huì)返回一個(gè)DisposableHandle對(duì)象,以便可以調(diào)用它的dispose方法進(jìn)行取消發(fā)送。
最后我使用Architecture Component與Coroutine寫(xiě)了個(gè)簡(jiǎn)單的Demo,大家可以在Github中進(jìn)行查看
源碼地址: 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
公眾號(hào)掃描二維碼,關(guān)注微信公眾號(hào),獲取獨(dú)家最新IT技術(shù)!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/75042.html
摘要:的定位屬于預(yù)處理器嗎還是屬于后置處理器都不是,因?yàn)榫唧w做的事取決于開(kāi)發(fā)者使用了什么插件。這里做一個(gè)我覺(jué)得比較恰當(dāng)?shù)念?lèi)比,中的相當(dāng)于的中的,,等預(yù)處理器相當(dāng)于,雖然不是完全合理,但是還是比較恰當(dāng)。 前言 原諒我取這樣的標(biāo)題,我知道 postCss 對(duì)于大多數(shù)前端開(kāi)發(fā)者來(lái)說(shuō)早已經(jīng)很熟悉了,但是樓主作為一個(gè)初出茅廬的前端er,還有好多的工具和技術(shù)沒(méi)接觸過(guò),說(shuō)來(lái)慚愧。雖然平時(shí)也喜歡使用css預(yù)...
摘要:的空安全設(shè)計(jì),主要是在類(lèi)型后面加表示可空,否則就不能為。換句話說(shuō),這里的提供了初始化的方法,不過(guò)真正初始化這個(gè)動(dòng)作發(fā)生的時(shí)機(jī)卻是在第一次被使用時(shí)了。至于技術(shù),實(shí)際上是的一個(gè)應(yīng)用,也就是屬性代理了。 1、Hello, Kotlin Bugly 技術(shù)干貨系列內(nèi)容主要涉及移動(dòng)開(kāi)發(fā)方向,是由 Bugly 邀請(qǐng)騰訊內(nèi)部各位技術(shù)大咖,通過(guò)日常工作經(jīng)驗(yàn)的總結(jié)以及感悟撰寫(xiě)而成,內(nèi)容均屬原創(chuàng),轉(zhuǎn)載請(qǐng)標(biāo)明...
摘要:原始的開(kāi)發(fā)模式已經(jīng)滿足不了呈指數(shù)增長(zhǎng)的需求了。它承擔(dān)起了模塊管理這一重要角色。是個(gè)前端小菜鳥(niǎo),接觸前端不到兩年時(shí)間,去年畢業(yè)正式參加工作。目前就職于杭州邊鋒網(wǎng)絡(luò)神盾局就是這么霸氣。 對(duì)于剛進(jìn)入前端領(lǐng)域的人,特別是還處于小白階段的初學(xué)者來(lái)說(shuō),很多人對(duì) webpack 并不熟知。就像 Light (對(duì),我就是 Light)一樣,剛接觸前端,最關(guān)心的就是樣式和簡(jiǎn)單的交互了。那時(shí)候怎么會(huì)知道像...
閱讀 2829·2021-10-13 09:48
閱讀 3801·2021-10-13 09:39
閱讀 3602·2021-09-22 16:04
閱讀 1837·2021-09-03 10:48
閱讀 847·2021-08-03 14:04
閱讀 2367·2019-08-29 15:18
閱讀 3411·2019-08-26 12:19
閱讀 2880·2019-08-26 12:08