---
# System prepended metadata

title: Kotlin Corourtine in Android
tags: [Android]

---

---
tags: Android
---

# Kotlin Corourtine in Android

## Coroutine
什麼是Coroutine? 先來暸解這個字是怎麼來的。其實它是由兩個字所組合而來，分別是cooperation(合作)以及routine(例行作業)這兩個字組合起來，其實就可以大致上理解它是合作的例行作業？吧，其實我個人倒是覺得講合作我能理解，至於例行作業我還在努力參透其中的意義，說不定某天會突然開竅(疑？)


## 輕量級Thread?
可以在網路上找到很多的說明，說它是輕量級的Thread。它能做的事情跟Thread一樣，能夠進行異步處理耗時的工作，但是我認為它其實不算是Thread。與其說是一個輕量級的Thread，不如說它是自動協助開發者控制Thread的物件，透過有效的控制達到消耗資源最少卻又同時達到目的的效果。

![](https://i.imgur.com/Uo5QDvc.png)

參考上圖，我們先來點Android異步操作的基本慨念。基本上我們都知道在android中，執行耗時的工作在UI Thread(Main Thread)中處理是被禁止的，需要處理耗時的工作就必須透過IO Thread，就如同上圖一樣。而在Coroution的操作中就帶很強烈的帶入了這樣的操作概念，可以透過區塊宣告的方式說明現在我想要執行在IO Thread中，什麼時候我想要切回UI Thread中，在實際撰寫與閱讀上就可以很直覺的知道，這點我個人是覺得非常棒。

## Coroutine 起手式

### Dependencies

先在Gradle中新增相關的dependencies。
``` =android
//kotlin coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
```

在使用上我個人覺得真的很簡便，概念上就像是宣告一個程式碼區塊是指定給coroutine執行專用的，就如同下方：

```=android
fun coroutineDemo() {
    CoroutineScope(Dispatchers.IO).launch {
            
    }
}
```
CoroutineScope代表協程運作的區塊，可以作用在IO或是Main Thread中，視需要自行設定。CoroutineScope內有一個CoroutineContext，這個指的是運作情境，可以透過Dispatchers指定，如上面程式碼所示。除了CoroutineScope還有一個常見的就是GlobalScope，這個Scope繼承CoroutineScope，他的生命週期跟隨者Appication，所以不會被隨意取消掉，一般建議使用上用CoroutineScope就可以了。

以下，我建立幾個方法用來模擬耗時的工作：

```=android
fun sleep1000(): String {
    Thread.sleep(1000)
    return "Sleep1000"
}

fun sleep2000(): String {
    Thread.sleep(2000)
    return "Sleep2000"
}

fun sleep3000(): String {
    Thread.sleep(3000)
    return "Sleep3000"
}
```

接著我們在scope中透過async這個方法來執行其中一個耗時工作：

```=android=1
CoroutineScope(Dispatchers.IO).launch {
    var deferred: Deferred<String> = async { sleep1000() }
    withContext(Dispatchers.Main) {
        textView.text = deferred.await()
    }
}
```

說明一下Coroutine裡面幾個關鍵字：

 - **runblock**: 啟動一個協程，並且block目前的Thread等待此協程執行完畢。
 - **launch**: 在不阻塞目前thread的情況下，啟動協程並return一個Job物件。(該Job物件可以用來控制協程。)
 - **wichContext**: 指定協程要運作的情境(Main or IO Thread)。
 - **async**: 在不阻塞目前thread的情況下，啟動協程並且return一個Deferred物件，需要配合await一起使用。

所以根據上面的程式碼，可以這樣解讀：
第1行 = 宣告一個coroutine的執行環境，並且要在IO Thread中執行。
第2行 = 啟動協程，執行sleep1000方法，並且我要回傳一個Defferrd物件。
第3行 = 宣告一個執行在Main Thread的區塊。
第4行 = 宣告textView.text獲得deferrd待sleep1000執行完所得到的結果。

是不是這樣看起來程式碼相當直觀？可以很線性的筆直閱讀下來。在這裡的async會在執行的時候直接先回傳一個deferred，並且該deferred物件會等待執行的結果取得後，再接著往下執行程式碼。

## 多個協程

如果同時執行多個協程可以嗎？當然可以。在scope中可以啟動多個斜程來執行耗時的工作。可以向下方這樣的寫法：

```=android=
CoroutineScope(Dispatchers.IO).launch {
    var deferred: Deferred<String> = async { sleep1000() }
    var deferred1: Deferred<String> = async { sleep2000() }
    var deferred2: Deferred<String> = async { sleep3000() }
    withContext(Dispatchers.Main) {
        textView.text = deferred.await()+deferred1.await()+deferred2.await()
    }
}
```
執行結果如下：

![](https://i.imgur.com/4eKfpez.png =300x500)

根據上面程式碼，在第6行可以把所有結果串起來，待最一個拿到回傳結果的deferred完成後，程式碼接著往下執行。

另外也可以把deferred寫在其他的協程裡面，讓協程等待deferred完成後再接著往下執行。

```=android=1
CoroutineScope(Dispatchers.IO).async {
    var deferred: Deferred<String> = async { sleep1000() }
    var deferred1: Deferred<String> = async { deferred.await()+sleep2000() }
    var deferred2: Deferred<String> = async { deferred1.await()+sleep3000() }
    withContext(Dispatchers.Main) {
        textView.text = deferred2.await()
    }
}
```

執行果如下：

![](https://i.imgur.com/RMrgOBm.png =300x500)

可以從上面的結果知道這樣總共需要執行6秒的時間才會完成所有的操作。

## corourtine的操控
上面介紹了coroutine的一些很基本的用法，但是如果今天我想要對某種情境下中斷停止協程的話因該怎麼做呢？上面有說到launch這個方法會回傳一個Job物件，要操控協程，就得靠它了。請參考下面展示的一段程式碼：

```=android=1
private lateinit var countTimeJob : Job
...
```
```=android=20
fun createCountTimeCoroutineScope() {
    countOver = false
    countTimeJob = CoroutineScope(Dispatchers.IO).launch {
        var i = 0;
        while (true) {
            if(countOver){
                cancel() //可以在內部取消
            }
            delay(1000)
            i++
            withContext(Dispatchers.Main){
                tvCountTime.text = i.toString()
            }
        }
    }
}

fun cancelCoroutine(){
    countTimeJob.cancel() //可以在外部取消
}
```

當然除了cancel這個取消的方法以外，還有以下的方法可以調用：
 - **join()**: 等待現在正在執行的job完成後，接著執行。
 - **cancalAndJoin()**: 其實就是取消正在執行的協程，重新執行協程。
 - **isActive()**: 確認協程是否在執行中。

以上就是coroutine一些簡單的基本用法。







