自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何正確的在 Android 上使用 Kotlin 協(xié)程?

移動(dòng)開發(fā) Android
在 Android 中,一般是不建議直接使用 GlobalScope 的。那么,在 Android 中應(yīng)該如何正確使用協(xié)程呢?再細(xì)分一點(diǎn),如何直接在 Activity 中使用呢?如何配合 ViewModel 、LiveData 、LifeCycle 等使用呢?我會(huì)通過簡單的示例代碼來闡述 Android 上的協(xié)程使用,你也可以跟著動(dòng)手敲一敲。

前言

你還記得是哪一年的 Google IO 正式宣布 Kotlin 成為 Android 一級(jí)開發(fā)語言嗎?是 Google IO 2017 。如今兩年時(shí)間過去了,站在一名 Android 開發(fā)者的角度來看,Kotlin 的生態(tài)環(huán)境越來越好了,相關(guān)的開源項(xiàng)目和學(xué)習(xí)資料也日漸豐富,身邊愿意去使用或者試用 Kotlin 的朋友也變多了。常年混跡掘金的我也能明顯感覺到 Kotlin 標(biāo)簽下的文章慢慢變多了(其實(shí)仍然少的可憐)。今年的 Google IO 也放出了 Kotlin First 的口號(hào),許多新的 API 和功能特性將優(yōu)先提供 Kotlin 支持。所以,時(shí)至今日,實(shí)在找不到安卓開發(fā)者不學(xué) Kotlin 的理由了。

今天想聊聊的是 Kotlin Coroutine。雖然在 Kotlin 發(fā)布之初就有了協(xié)程,但是直到 2018 年的 KotlinConf 大會(huì)上,JetBrain 發(fā)布了 Kotlin1.3RC,這才帶來了穩(wěn)定版的協(xié)程。即使穩(wěn)定版的協(xié)程已經(jīng)發(fā)布了一年之余,但是好像并沒有足夠多的用戶,至少在我看來是這樣。在我學(xué)習(xí)協(xié)程的各個(gè)階段中,遇到問題都鮮有地方可以求助,拋到技術(shù)群基本就石沉大海了?;局荒芸恳恍┯⑽奈臋n來解決問題。

在看完官方文檔的很長一段時(shí)間,我?guī)缀踔恢?GlobalScope。的確,官方文檔上基本從頭到尾都是在用 GlobalScope 寫示例代碼。所以一部分開發(fā)者,也包括我自己,在寫自己的代碼時(shí)也就直接 GlobalScope 了。一次偶然的機(jī)會(huì)才發(fā)現(xiàn)其實(shí)這樣的問題是很大的。在 Android 中,一般是不建議直接使用 GlobalScope 的。那么,在 Android 中應(yīng)該如何正確使用協(xié)程呢?再細(xì)分一點(diǎn),如何直接在 Activity 中使用呢?如何配合 ViewModel 、LiveData 、LifeCycle 等使用呢?我會(huì)通過簡單的示例代碼來闡述 Android 上的協(xié)程使用,你也可以跟著動(dòng)手敲一敲。

[[279995]]

協(xié)程在 Android 上的使用

GlobalScope

在一般的應(yīng)用場景下,我們都希望可以異步進(jìn)行耗時(shí)任務(wù),比如網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)處理等等。當(dāng)我們離開當(dāng)前頁面的時(shí)候,也希望可以取消正在進(jìn)行的異步任務(wù)。這兩點(diǎn),也正是使用協(xié)程中所需要注意的。既然不建議直接使用 GlobalScope,我們就先試驗(yàn)一下使用它會(huì)是什么效果。

  1. private fun launchFromGlobalScope() { 
  2.  GlobalScope.launch(Dispatchers.Main) { 
  3.  val deferred = async(Dispatchers.IO) { 
  4.  // network request 
  5.  delay(3000) 
  6.  "Get it" 
  7.  } 
  8.  globalScope.text = deferred.await() 
  9.  Toast.makeText(applicationContext, "GlobalScope", Toast.LENGTH_SHORT).show() 
  10.  } 

在 launchFromGlobalScope() 方法中,我直接通過 GlobalScope.launch() 啟動(dòng)一個(gè)協(xié)程,delay(3000) 模擬網(wǎng)絡(luò)請(qǐng)求,三秒后,會(huì)彈出一個(gè) Toast 提示。使用上是沒有任何問題的,可以正常的彈出 Toast 。但是當(dāng)你執(zhí)行這個(gè)方法之后,立即按返回鍵返回上一頁面,仍然會(huì)彈出 Toast 。如果是實(shí)際開發(fā)中通過網(wǎng)絡(luò)請(qǐng)求更新頁面的話,當(dāng)用戶已經(jīng)不在這個(gè)頁面了,就根本沒有必要再去請(qǐng)求了,只會(huì)浪費(fèi)資源。GlobalScope 顯然并不符合這一特性。https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html 中其實(shí)也詳細(xì)說明了,如下所示: 

  1. Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Another use of the global scope is operators running in Dispatchers.Unconfined, which don’t have any job associated with them. 
  2.  
  3. Application code usually should use an application-defined CoroutineScope. Using async or launch on the instance of GlobalScope is highly discouraged. 

大致意思是,Global scope 通常用于啟動(dòng)頂級(jí)協(xié)程,這些協(xié)程在整個(gè)應(yīng)用程序生命周期內(nèi)運(yùn)行,不會(huì)被過早地被取消。程序代碼通常應(yīng)該使用自定義的協(xié)程作用域。直接使用 GlobalScope 的 async 或者 launch 方法是強(qiáng)烈不建議的。

GlobalScope 創(chuàng)建的協(xié)程沒有父協(xié)程,GlobalScope 通常也不與任何生命周期組件綁定。除非手動(dòng)管理,否則很難滿足我們實(shí)際開發(fā)中的需求。所以,GlobalScope 能不用就盡量不用。

MainScope

官方文檔中提到要使用自定義的協(xié)程作用域,當(dāng)然,Kotlin 已經(jīng)給我們提供了合適的協(xié)程作用域 MainScope ??匆幌?MainScope 的定義:

  1. public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main) 

記著這個(gè)定義,在后面 ViewModel 的協(xié)程使用中也會(huì)借鑒這種寫法。

給我們的 Activity 實(shí)現(xiàn)自己的協(xié)程作用域:

  1. class BasicCorotineActivity : AppCompatActivity(), CoroutineScope by MainScope() {} 

通過擴(kuò)展函數(shù) launch() 可以直接在主線程中啟動(dòng)協(xié)程,示例代碼如下:

  1. private fun launchFromMainScope() { 
  2.  launch { 
  3.  val deferred = async(Dispatchers.IO) { 
  4.  // network request 
  5.  delay(3000) 
  6.  "Get it" 
  7.  } 
  8.  mainScope.text = deferred.await() 
  9.  Toast.makeText(applicationContext, "MainScope", Toast.LENGTH_SHORT).show() 
  10.  } 

最后別忘了在 onDestroy() 中取消協(xié)程,通過擴(kuò)展函數(shù) cancel() 來實(shí)現(xiàn):

  1. override fun onDestroy() { 
  2.  super.onDestroy() 
  3.  cancel() 

現(xiàn)在來測試一下 launchFromMainScope() 方法吧!你會(huì)發(fā)現(xiàn)這完全符合你的需求。實(shí)際開發(fā)中可以把 MainScope 整合到 BaseActivity 中,就不需要重復(fù)書寫模板代碼了。

ViewModelScope

如果你使用了 MVVM 架構(gòu),根本就不會(huì)在 Activity 上書寫任何邏輯代碼,更別說啟動(dòng)協(xié)程了。這個(gè)時(shí)候大部分工作就要交給 ViewModel 了。那么如何在 ViewModel 中定義協(xié)程作用域呢?還記得上面 MainScope() 的定義嗎?沒錯(cuò),搬過來直接使用就可以了。

  1. class ViewModelOne : ViewModel() { 
  2.  private val viewModelJob = SupervisorJob() 
  3.  private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) 
  4.  val mMessage: MutableLiveData<String> = MutableLiveData() 
  5.  fun getMessage(message: String) { 
  6.  uiScope.launch { 
  7.  val deferred = async(Dispatchers.IO) { 
  8.  delay(2000) 
  9.  "post $message" 
  10.  } 
  11.  mMessage.value = deferred.await() 
  12.  } 
  13.  } 
  14.  override fun onCleared() { 
  15.  super.onCleared() 
  16.  viewModelJob.cancel() 
  17.  } 

這里的 uiScope 其實(shí)就等同于 MainScope。調(diào)用 getMessage() 方法和之前的 launchFromMainScope() 效果也是一樣的,記得在 ViewModel 的 onCleared() 回調(diào)里取消協(xié)程。

你可以定義一個(gè) BaseViewModel 來處理這些邏輯,避免重復(fù)書寫模板代碼。然而 Kotlin 就是要讓你做同樣的事,寫更少的代碼,于是 viewmodel-ktx 來了??吹?ktx ,你就應(yīng)該明白它是來簡化你的代碼的。引入如下依賴:

  1. implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha03" 

然后,什么都不需要做,直接使用協(xié)程作用域 viewModelScope 就可以了。viewModelScope 是 ViewModel 的一個(gè)擴(kuò)展屬性,定義如下:

  1. val ViewModel.viewModelScope: CoroutineScope 
  2.  get() { 
  3.  val scope: CoroutineScope? = this.getTag(JOB_KEY) 
  4.  if (scope != null) { 
  5.  return scope 
  6.  } 
  7.  return setTagIfAbsent(JOB_KEY, 
  8.  CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main)) 
  9.  } 

看下代碼你就應(yīng)該明白了,還是熟悉的那一套。當(dāng) ViewModel.onCleared() 被調(diào)用的時(shí)候,viewModelScope 會(huì)自動(dòng)取消作用域內(nèi)的所有協(xié)程。使用示例如下:

  1. fun getMessageByViewModel() { 
  2.  viewModelScope.launch { 
  3.  val deferred = async(Dispatchers.IO) { getMessage("ViewModel Ktx") } 
  4.  mMessage.value = deferred.await() 
  5.  } 

寫到這里,viewModelScope 是能滿足需求的最簡寫法了。實(shí)際上,寫完全篇,viewModelScope 仍然是我認(rèn)為的最好的選擇。

LiveData

Kotlin 同樣為 LiveData 賦予了直接使用協(xié)程的能力。添加如下依賴:

  1. implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03" 

直接在 liveData {} 代碼塊中調(diào)用需要異步執(zhí)行的掛起函數(shù),并調(diào)用 emit() 函數(shù)發(fā)送處理結(jié)果。示例代碼如下所示:

  1. val mResult: LiveData<String> = liveData { 
  2.  val string = getMessage("LiveData Ktx"
  3.  emit(string) 

你可能會(huì)好奇這里好像并沒有任何的顯示調(diào)用,那么,liveData 代碼塊是在什么執(zhí)行的呢?當(dāng) LiveData 進(jìn)入 active 狀態(tài)時(shí),liveData{ } 會(huì)自動(dòng)執(zhí)行。當(dāng) LiveData 進(jìn)入 inactive 狀態(tài)時(shí),經(jīng)過一個(gè)可配置的 timeout 之后會(huì)自動(dòng)取消。如果它在完成之前就取消了,當(dāng) LiveData 再次 active 的時(shí)候會(huì)重新運(yùn)行。如果上一次運(yùn)行成功結(jié)束了,就不會(huì)再重新運(yùn)行。也就是說只有自動(dòng)取消的 liveData{ } 可以重新運(yùn)行。其他原因(比如 CancelationException)導(dǎo)致的取消也不會(huì)重新運(yùn)行。

所以 livedata-ktx 的使用是有一定限制的。對(duì)于需要用戶主動(dòng)刷新的場景,就無法滿足了。在一次完整的生命周期內(nèi),一旦成功執(zhí)行完成一次,就沒有辦法再觸發(fā)了。 這句話不知道對(duì)不對(duì),我個(gè)人是這么理解的。因此,還是 viewmodel-ktx 的適用性更廣,可控性也更好。

LifecycleScope

  1. implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha03" 

lifecycle-runtime-ktx 給每個(gè) LifeCycle 對(duì)象通過擴(kuò)展屬性定義了協(xié)程作用域 lifecycleScope 。你可以通過 lifecycle.coroutineScope 或者 lifecycleOwner.lifecycleScope 進(jìn)行訪問。示例代碼如下:

  1. fun getMessageByLifeCycle(lifecycleOwner: LifecycleOwner) { 
  2.  lifecycleOwner.lifecycleScope.launch { 
  3.  val deferred = async(Dispatchers.IO) { getMessage("LifeCycle Ktx") } 
  4.  mMessage.value = deferred.await() 
  5.  } 

當(dāng) LifeCycle 回調(diào) onDestroy() 時(shí),協(xié)程作用域 lifecycleScope 會(huì)自動(dòng)取消。在 Activity/Fragment 等生命周期組件中我們可以很方便的使用,但是在 MVVM 中又不會(huì)過多的在 View 層進(jìn)行邏輯處理,viewModelScope 基本就可以滿足 ViewModel 中的需求了,lifecycleScope 也顯得有點(diǎn)那么食之無味。但是他有一個(gè)特殊的用法:

  1. suspend fun <T> Lifecycle.whenCreated() 
  2. suspend fun <T> Lifecycle.whenStarted() 
  3. suspend fun <T> Lifecycle.whenResumed() 
  4. suspend fun <T> LifecycleOwner.whenCreated() 
  5. suspend fun <T> LifecycleOwner.whenStarted() 
  6. suspend fun <T> LifecycleOwner.whenResumed() 

可以指定至少在特定的生命周期之后再執(zhí)行掛起函數(shù),可以進(jìn)一步減輕 View 層的負(fù)擔(dān)。

 

責(zé)任編輯:未麗燕 來源: 安卓巴士
相關(guān)推薦

2023-10-24 19:37:34

協(xié)程Java

2020-06-19 08:01:48

Kotlin 協(xié)程編程

2020-07-07 09:19:28

Android 協(xié)程開發(fā)

2020-04-08 09:06:34

Android 協(xié)程開發(fā)

2020-04-23 09:33:32

Android 協(xié)程開發(fā)

2021-05-20 09:14:09

Kotlin協(xié)程掛起和恢復(fù)

2021-09-16 09:59:13

PythonJavaScript代碼

2021-08-04 16:19:55

AndroidKotin協(xié)程Coroutines

2023-09-03 19:13:29

AndroidKotlin

2020-02-19 14:16:23

kotlin協(xié)程代碼

2023-11-17 11:36:59

協(xié)程纖程操作系統(tǒng)

2021-04-28 09:08:23

Kotlin協(xié)程代碼

2020-02-24 10:39:55

Python函數(shù)線程池

2024-02-05 09:06:25

Python協(xié)程Asyncio庫

2024-06-27 07:56:49

2022-08-10 13:12:04

Linuxcat命令

2025-02-08 09:13:40

2025-02-28 09:04:08

2014-04-09 09:32:24

Go并發(fā)

2021-12-09 06:41:56

Python協(xié)程多并發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)