摘要:例如,在方面它主要能夠幫助你解決以下兩個問題在主線程中執行耗時任務導致的主線程阻塞,從而使發生。提供主線程安全,同時對來自于主線程的網絡回調磁盤操提供保障。在線程通過從數據庫取數據,一旦數據返回,在主線程進行處理。
今天我們來聊聊Kotlin Coroutine,如果你還沒有了解過,那么我要提前恭喜你,因為你將掌握一個新技能,對你的代碼方面的提升將是很好的助力。
What Coroutine簡單的來說,Coroutine是一個并發的設計模式,你能通過它使用更簡潔的代碼來解決異步問題。
例如,在Android方面它主要能夠幫助你解決以下兩個問題:
在主線程中執行耗時任務導致的主線程阻塞,從而使App發生ANR。
提供主線程安全,同時對來自于主線程的網絡回調、磁盤操提供保障。
這些問題,在接下來的文章中我都會給出解決的示例。
Callback說到異步問題,我們先來看下我們常規的異步處理方式。首先第一種是最基本的callback方式。
callback的好處是使用起來簡單,但你在使用的過程中可能會遇到如下情形
GatheringVoiceSettingRepository.getInstance().getGeneralSettings(RequestLanguage::class.java) .observe(this, { language -> convertResult(language, { enable -> // todo something }) })
這種在其中一個callback中回調另一個callback回調,甚至更多的callback都是可能存在。這些情況導致的問題是代碼間的嵌套層級太深,導致邏輯嵌套復雜,后續的維護成本也要提高,這不是我們所要看到的。
那么有什么方法能夠解決呢?當然有,其中的一種解決方法就是我接下來要說的第二種方式。
Rx系列對多嵌套回調,Rx系列在這方面處理的已經非常好了,例如RxJava。下面我們來看一下RxJava的解決案例
disposable = createCall().map { // return RequestType }.subscribeWith(object : SMDefaultDisposableObserver{ override fun onNext(t: RequestType) { // todo something } })
RxJava豐富的操作符,再結合Observable與Subscribe能夠很好的解決異步嵌套回調問題。但是它的使用成本就相對提高了,你要對它的操作符要非常了解,避免在使用過程中濫用或者過度使用,這樣自然復雜度就提升了。
那么我們渴望的解決方案是能夠更加簡單、全面與健壯,而我們今天的主題Coroutine就能夠達到這種效果。
Coroutine在Kotlin中的基本要點在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: 從它被掛起的位置繼續執行,并且掛起時保存的數據也被還原
解釋的有點生硬,簡單的來說就是suspend可以將該任務掛起,使它暫時不在調用的線程中,以至于當前線程可以繼續執行別的任務,一旦被掛起的任務已經執行完畢,那么就會通過resume將其重新插入到當前線程中。
所以上面的示例展示的是,當get還在請求的時候,fetch方法將會被掛起,直到get結束,此時才會插入到主線程中并返回結果。
一圖勝千言,我做了一張圖,希望能有所幫助。
另外需要注意的是,suspend方法只能夠被其它的suspend方法調用或者被一個coroutine調用,例如launch。
Dispatchers另一方面Coroutine使用Dispatchers來負責調度協調程序執行的線程,這一點與RxJava的schedules有點類似,但不同的是Coroutine一定要執行在Dispatchers調度中,因為Dispatchers將負責resume被suspend的任務。
Dispatchers提供三種模式切換,分別為
Dispatchers.Main: 使Coroutine運行中主線程,以便UI操作
Dispatchers.IO: 使Coroutine運行在IO線程,以便執行網絡或者I/O操作
Dispatchers.Default: 在主線程之外提高對CPU的利用率,例如對list的排序或者JSON的解析。
再來看上面的示例
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函數。
創建Coroutine上面提到suspend函數只能在相應的suspend中或者Coroutine中調用。那么Coroutine又該如何創建呢?
有兩種方式,分別為launch與async
launch: 開啟一個新的Coroutine,但不返回結果
async: 開啟一個新的Coroutine,但返回結果
還是上面的例子,如果我們需要執行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 Components如果你使用了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}" }ViewModelScope
在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已經清除了,那么數據獲取也會停止,防止資源的浪費。
LifecycleScope對于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都能達到同樣的效果。
LiveDataLiveData中可以直接使用liveData,在它的參數中會調用一個suspend函數,同時會返回LiveData對象
funliveData( context: CoroutineContext = EmptyCoroutineContext, timeoutInMs: Long = DEFAULT_TIMEOUT, @BuilderInference block: suspend LiveDataScope .() -> Unit ): LiveData = 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技術!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75042.html
摘要:的定位屬于預處理器嗎還是屬于后置處理器都不是,因為具體做的事取決于開發者使用了什么插件。這里做一個我覺得比較恰當的類比,中的相當于的中的,,等預處理器相當于,雖然不是完全合理,但是還是比較恰當。 前言 原諒我取這樣的標題,我知道 postCss 對于大多數前端開發者來說早已經很熟悉了,但是樓主作為一個初出茅廬的前端er,還有好多的工具和技術沒接觸過,說來慚愧。雖然平時也喜歡使用css預...
摘要:的空安全設計,主要是在類型后面加表示可空,否則就不能為。換句話說,這里的提供了初始化的方法,不過真正初始化這個動作發生的時機卻是在第一次被使用時了。至于技術,實際上是的一個應用,也就是屬性代理了。 1、Hello, Kotlin Bugly 技術干貨系列內容主要涉及移動開發方向,是由 Bugly 邀請騰訊內部各位技術大咖,通過日常工作經驗的總結以及感悟撰寫而成,內容均屬原創,轉載請標明...
摘要:原始的開發模式已經滿足不了呈指數增長的需求了。它承擔起了模塊管理這一重要角色。是個前端小菜鳥,接觸前端不到兩年時間,去年畢業正式參加工作。目前就職于杭州邊鋒網絡神盾局就是這么霸氣。 對于剛進入前端領域的人,特別是還處于小白階段的初學者來說,很多人對 webpack 并不熟知。就像 Light (對,我就是 Light)一樣,剛接觸前端,最關心的就是樣式和簡單的交互了。那時候怎么會知道像...
閱讀 2815·2021-10-13 09:48
閱讀 3776·2021-10-13 09:39
閱讀 3586·2021-09-22 16:04
閱讀 1816·2021-09-03 10:48
閱讀 837·2021-08-03 14:04
閱讀 2358·2019-08-29 15:18
閱讀 3400·2019-08-26 12:19
閱讀 2869·2019-08-26 12:08