Koltin系列 - 協程從認識到安卓中的使用(五)

Kotlin細節文章筆記整理更新進度:
Kotlin系列 - 基礎類型結構細節小結(一)
Kotlin系列 - 函數與類相關細節小結(二)
Kotlin系列 - 高階函數與標準庫中的經常使用函數(三)
Kotlin系列 - 進階深刻泛型協變逆變從java到Kotlin(四)java

前言

學習了Kotlin一整個系列了,可是協程這塊遲遲沒有整理成一篇博文。誒,最近狀態有點不對 >_< || 。 可是不管如何,必定要加油!!最後一篇要劃上個完美點的句號,撒個漂亮點的花。android

關於協程的一個點在這裏跟你們先說一下,協程並不是什麼很深奧的東西,說白了也是在線程上面的產物,並不是憑空產生的一個新的概念。官網講得可能有點高大上了,不過實際上你就當是它幫咱們使用了線程池Handler進行一些自動切換線程的邏輯封裝進而造成了這樣子的一種API吧~~git

協程的一些基礎使用

添加基本的依賴github

implementation  'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
複製代碼

GlobalScope

官網定義: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. Application code usually should use an application-defined CoroutineScope. Using async or launch on the instance of GlobalScope is highly discouraged. 大體意思就是:這個通常被用於頂級的協程,application生命週期級別的,不會過早的被取消。應用程序一般應該使用一個應用程序定義的CoroutineScope。使用異步或啓動的實例GlobalScope很是氣餒(不建議的)。api

先來模擬一個場景,在一個ActivityA調用globalScopeLaunch,或者globalScopeLaunch進行耗時操做,相似IO操做或者網絡請求等。而後在它尚未返回的時候銷燬ActivityA再跳轉到ActivityBbash

fun globalScopeLaunch(){
        GlobalScope.launch(Dispatchers.Main) {
            delay(5000)
            Toast.makeText(this@MainActivity,"等待五秒彈出~~~",Toast.LENGTH_LONG).show()
        }
    }
複製代碼

你會發現,它照樣會彈出這個Toast,可是這樣子其實並不是咱們想要的結果。有些事務咱們應該隨着組件的生命週期結束而結束。不然一會形成資源的浪費或者內存泄露。(這裏有個問題,若是你在生命週期結束的時候手動關閉的話,那就能夠避免這種狀況。可是這裏就涉及到要你本身手動來控制了)網絡

private fun globalScopeLaunch1(){
        GlobalScope.launch(Dispatchers.Main) {
            launch (Dispatchers.Main){
                delay(1000)
                Toast.makeText(this@MainActivity,"等待一秒彈出",Toast.LENGTH_LONG).show()
            }
            Toast.makeText(this@MainActivity,"立馬彈出~~~",Toast.LENGTH_LONG).show()
            delay(5000)
            Toast.makeText(this@MainActivity,"等待五秒彈出~~~",Toast.LENGTH_LONG).show()
       }
    }
複製代碼

globalScopeLaunch1中,立馬彈出~~~ ->等待一秒彈出 ->"等待五秒彈出~~~。這裏之因此會先彈出來立馬彈出~~~這個信息。由於協程中,又開了一個新的協程,新的協程阻塞一秒不關外邊協程的事情,外邊協程繼續執行。併發

private fun globalScopeLaunch(){
        job =  GlobalScope.launch(Dispatchers.Main) {
            launch (Dispatchers.Main){
                runBlocking {//加了runBlocking這個協程做用域
                delay(1000)
                Toast.makeText(this@MainActivity,"等待一秒彈出",Toast.LENGTH_LONG).show()
                }
            }
            Toast.makeText(this@MainActivity,"立馬彈出~~~",Toast.LENGTH_LONG).show()
            delay(5000)
            Toast.makeText(this@MainActivity,"等待五秒彈出~~~",Toast.LENGTH_LONG).show()
       }
    }
複製代碼

runBlocking會阻塞致使立馬彈出~~~這個Toast不會馬上顯示出來,而是等了1秒後,再彈出來。app

上面的代碼能夠簡化一下 在協程做用域中,可使用withContext(Dispatchers.Main)替換launch (Dispatchers.Main)異步

job =  GlobalScope.launch(Dispatchers.Main) {
            withContext(Dispatchers.Main){} 
}
複製代碼

協程做用域

GlobalScope.launch(Dispatchers.Main)這裏我是分發到主線程Main上面進行delay可是並不會形成ANR,能夠簡單看一下launch怎麼調用的

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
複製代碼

第三個參數 : block: suspend CoroutineScope.() 表示使用的協程做用域是CoroutineScope並不會形成阻塞。這裏的阻塞是指協程做用域外的代碼阻塞,協程做用域內部還會被阻塞的。 CoroutineScopeGlobalScope的父類~

兩種協程做用域,以及結構化併發.png

協程的啓動方式launch與Async

private fun globalScopeAsync(){
  GlobalScope.launch(Dispatchers.Main){
  val deferred = async(IO) {
                Thread.sleep(5000)
                "等待五秒彈出~~~"
            }
    Toast.makeText(this@MainActivity,"立馬先彈出來~~",Toast.LENGTH_LONG).show()//這句是來驗證sync是不會阻塞改async協程外的代碼的
    val message =  deferred.await()
    Toast.makeText(this@MainActivity,message,Toast.LENGTH_LONG).show()
  }
}
複製代碼

async會異步跑該做用域外層的協程的邏輯,咱們能夠看到"立馬先彈出來~~"彈出框會先彈出來,再等過五秒在彈出 "等待五秒彈出~~~"再彈出來。在await這裏會阻塞等待deferred返回回來再繼續接下來的操做。

協程的啓動方式.png

協程分發

image.png

協程的取消

獲取到對應協程的Job對象,調用cancel()

var job = GlobalScope.launch(Dispatchers.Main) { }
job.cancel()
//Deferred的對象父類是Job
var deferred =  GlobalScope.async {  }
deferred.cancel()
複製代碼

Android上使用協程的正確姿式

MainScope

上面Global的官方定義中已經提示咱們使用自定義的協程。 MainScopekotlin爲咱們自定義好的一個協程做用域。 代碼定義:

@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
複製代碼

基本使用:

class MyAndroidActivity {
  private val scope = MainScope()
 //使用MainScope並賦予它名字
// val mainScope = MainScope() + CoroutineName(this.javaClass.name)
  private fun mainScopeLaunch(){
        scope.launch {}
  }
  override fun onDestroy() {
    super.onDestroy()
    scope.cancel()
  }
}
複製代碼

能夠將這邏輯放到base類中

//無需定義協程名字的時候
open class BaseCoroutineScopeActivity : AppCompatActivity() , CoroutineScope by MainScope()

class MainActivity : BaseCoroutineScopeActivity(){

 private fun mainScopeLaunch(){
        launch {  }
    }
override fun onDestroy() {
        super.onDestroy()
        cancel()
    }
}
-----------------------------------------------------------------
//定義協程名字的時候
open class BaseCoroutineScopeActivity : AppCompatActivity() {
    val mainLaunch =  MainScope()+ CoroutineName(this.javaClass.simpleName)
}

class MainActivity : BaseCoroutineScopeActivity(){
 private fun mainScopeLaunch(){
        mainLaunch.launch {  }
    }
override fun onDestroy() {
        super.onDestroy()
        mainLaunch.cancel()
    }
}

複製代碼

ViewModelScope

使用該協程首先要導入包

api 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc02'
複製代碼

代碼定義:

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
val ViewModel.viewModelScope: CoroutineScope
        get() {
            val scope: CoroutineScope? = this.getTag(JOB_KEY)
            if (scope != null) {
                return scope
            }
            return setTagIfAbsent(JOB_KEY,
                CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
        }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}
複製代碼

這段代碼跟MainScope同樣,只是外面多了一層CloseableCoroutineScope的封裝,這個是爲何呢?? 咱們進去setTagIfAbsent看一下

<T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {
            closeWithRuntimeException(result);
        }
        return result;
    }
複製代碼

這裏能夠看出,當mCleared = true的時候它會自動幫咱們關閉掉viewModelScope,也就是它幫咱們處理生命週期的問題了 咱們只管使用就能夠。

使用:

fun requestAhuInfo() {
         viewModelScope.launch {         }
    }
複製代碼

LiveData && LifecycleScope 這兩個我本身並無使用。

推薦一下

秉心說TM的 - 如何正確的在 Android 上使用協程 ? 裏面有說了這幾種kotlin爲咱們提供的協程

協程中的多種任務狀況

  • 多個任務串行( launch+ withContext多個)
viewModelScope.launch {
            var result1 = withContext(Dispatchers.IO) {
                Log.i(TAG, "result1-1")
                Log.i(TAG, "result1-2")
                Thread.sleep(4000)
                Log.i(TAG, "result1-3")
                "Hello"
            }
            var result2 = withContext(Dispatchers.IO) {
                Log.i(TAG, "result2-1")
                Log.i(TAG, "result2-2")
                Log.i(TAG, "result2-3")
                "world"
            }
            val result = result1 + result2
            Log.i(TAG, result)
        }
------------------------------------------------------------------------------------
2020-03-23 19:19:40.587 11021-11096/com.ldr.testcoroutines I/MainViewModel: result1-1
2020-03-23 19:19:40.587 11021-11096/com.ldr.testcoroutines I/MainViewModel: result1-2
2020-03-23 19:19:44.592 11021-11096/com.ldr.testcoroutines I/MainViewModel: result1-3
2020-03-23 19:19:44.602 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-1
2020-03-23 19:19:44.603 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-2
2020-03-23 19:19:44.603 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-3
2020-03-23 19:19:44.603 11021-11021/com.ldr.testcoroutines I/MainViewModel: Helloworld
複製代碼
  • 多個任務並行( launch+ async多個)(launch + launch多個)
viewModelScope.launch {
            val deferred = async(Dispatchers.IO) {
                Log.i(TAG, "result1-1")
                Log.i(TAG, "result1-2")
                Thread.sleep(4000)
                Log.i(TAG, "result1-3")
                "Hello"
            }
            val deferred1 = async {
                Log.i(TAG, "result2-1")
                Log.i(TAG, "result2-2")
                Log.i(TAG, "result2-3")
                "world"
            }
            var str = deferred.await() + deferred1.await()
            Log.i(TAG, str)
        }
------------------------------------------------------------------------------------
2020-03-25 13:36:00.736 6221-6253/com.ldr.testcoroutines I/MainViewModel: result1-1
2020-03-25 13:36:00.736 6221-6253/com.ldr.testcoroutines I/MainViewModel: result1-2
2020-03-25 13:36:00.737 6221-6221/com.ldr.testcoroutines I/MainViewModel: result2-1
2020-03-25 13:36:00.737 6221-6221/com.ldr.testcoroutines I/MainViewModel: result2-2
2020-03-25 13:36:00.737 6221-6221/com.ldr.testcoroutines I/MainViewModel: result2-3
2020-03-25 13:36:04.738 6221-6253/com.ldr.testcoroutines I/MainViewModel: result1-3
2020-03-25 13:36:04.750 6221-6221/com.ldr.testcoroutines I/MainViewModel: Helloworld

複製代碼
viewModelScope.launch {
            launch(Dispatchers.IO) {
                Log.i(TAG, "result1-1")
                Log.i(TAG, "result1-2")
                Thread.sleep(4000)
                Log.i(TAG, "result1-3")
            }

            launch(Dispatchers.IO) {
                Log.i(TAG, "result2-1")
                Log.i(TAG, "result2-2")
                Log.i(TAG, "result2-3")
            }
        }
---------------------------------------------------------------------------------
2020-03-23 19:21:29.781 11021-11128/com.ldr.testcoroutines I/MainViewModel: result1-1
2020-03-23 19:21:29.781 11021-11128/com.ldr.testcoroutines I/MainViewModel: result1-2
2020-03-23 19:21:29.782 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-1
2020-03-23 19:21:29.782 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-2
2020-03-23 19:21:29.782 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-3
2020-03-23 19:21:33.782 11021-11128/com.ldr.testcoroutines I/MainViewModel: result1-3
複製代碼

協程的異常處理

  • CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
        println("Caught $exception")
    }

    fun catchFun(): Unit {
        viewModelScope.launch(handler) {
            throw IOException()
        }
    }

 fun catch2Fun(): Unit {
        viewModelScope.launch(handler) {
            launch(Dispatchers.IO) {
                withContext(Dispatchers.Main){
                    throw IOException()
                }
            }
        }
    }
----------------------------------------------------------------------------------
2020-03-23 19:57:21.205 12038-12096/com.ldr.testcoroutines I/System.out: Caught java.io.IOException
2020-03-23 19:59:23.221 12038-12096/com.ldr.testcoroutines I/System.out: Caught java.io.IOException
複製代碼

通過上面的測試,能夠知道CoroutineExceptionHandler這種方法能夠將多層嵌套下的異常也捕獲到。

  • try { }catch(){}
//錯誤的寫法 協程外部是捕獲不到異常的
    fun catch1Fun(): Unit {
        try {
            viewModelScope.launch(Dispatchers.Main) {
                throw IOException()
            }
        }catch (e:Exception){
            Log.i(this.javaClass.name,e.cause?.message?:"拋出了異常")
        }
    }
//正確的寫法 好吧,,,,我以爲我在說廢話。。。
 fun catch1Fun(): Unit {
  viewModelScope.launch(Dispatchers.Main) {
     try {
           throw IOException()
        }catch (e:Exception){
            Log.i(this.javaClass.name,e.cause?.message?:"拋出了異常")
        }
    }
 }
複製代碼

附帶源碼地址: github.com/lovebluedan…

總結

以上就是簡單的介紹了一下,協程的一些基本用法,關於裏面不少原理性的東西,之後有機會再寫吧~~ 說實話,我並無用很深刻,因此不少細節的東西還沒理解好。以往能夠寫的深奧點,少點廢話少點代碼,文章寫得精煉點~~

相關文章
相關標籤/搜索