【Android】はじめてのCoroutines

アプリ開発歴( =プログラミング歴)がもうすぐで1年になります
@iTakahiroです。

現在はiOS・Androidの二刀流を目指して日々奮闘中です！
Qiitaでは、Androidの基本的なアーキテクチャであるMVVMに関する記事をシリーズ化して投稿し、1つのごくごく簡単なアプリを更新していくことでアウトプットしています。

**これまでの記事** :

- [【Android】分かった気になれる！アーキテクチャ・MVVM概説](https://qiita.com/iTakahiro/items/6b1b22efa69e55cea3fa) 
- [【Android】はじめてのDataBinding](https://qiita.com/iTakahiro/items/b5fe2b186750c6e774e5)
- [【Android】はじめてのRoom](https://qiita.com/iTakahiro/items/7e0d63140ae4dac10d18)

**ソースコード**

- [ArchitectureFootball](https://github.com/iTakahiro/ArchitectureFootball)

今回はMVVMの実装からは少し距離を置き、`Coroutines`について記事を書きます。
本記事では、[Coroutines：JetBrains公式ガイド](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html), [coroutines：JetBrains公式サンプルプロジェクト](https://github.com/kotlin/kotlinx.coroutines/blob/master/README.md#using-in-your-projects) の内容を掻い摘んで取り上げています。
公式ドキュメントは英語ですが詳しい手順やメソッドの説明などが記載されているため、もっと詳しい説明が必要な場合は参照してみてください。

また、ご指摘や感想などもコメントいただけると嬉しいです。

**アジェンダ**

- [はじめに](#はじめに)：`Coroutines`やその他の非同期処理について
- [導入方法](#導入方法)：`Coroutines`の導入方法
- [各種キーワード](#各種キーワード)：各種機能の説明と実装
- [まとめ](#まとめ)

# はじめに
### Coroutinesとは
`Coroutines`とは、Kotlinを開発したJetBrains社によって提供されている**非同期処理ライブラリ**で、軽量な非同期処理を扱います。
非同期処理の実装はGoogleが提供する[Thread](https://www.google.com/url?client=internal-element-cse&cx=000521750095050289010:zpcpi1ea4s8&q=https://developer.android.com/reference/java/lang/Thread&sa=U&ved=2ahUKEwiRz6HEk4_nAhXPwYsBHUuMBykQFjAAegQIABAB&usg=AOvVaw1nMIY5mjbD8ncNqPvjTXEK)や[AsyncTask](https://developer.android.com/reference/android/os/AsyncTask)などのクラスで実装することも可能ですが、以下の特徴・理由を考慮し、アーキテクチャ学習用のアプリでは`Coroutines`を採用しました。

- 非同期処理ごとにクラスを作成する必要がない。

そのため

- より安全でエラーが起こりにくい
- 作成したクラスが残ってしまうことによってリークを起こすことがない
  - `Thread`や`AsyncTask`を使用する場合、作成したクラスからライフサイクルが終了したActivityやViewModel内のメソッドを呼び出されリークするリスクが少なからず存在する
- コードが簡潔
- メモリのコストが小さい

### Thread, AsyncTaskとは
ThreadとAsyncTaskに関しても、簡単に説明をしておきます。

`Thread`は、単純に新しいスレッドを作成するクラスです。
そもそもスレッドの概念が分からん! という方は[こちら](https://developer.android.com/topic/performance/threads?hl=ja)のGoogle公式ドキュメントを参照したり「スレッド」で検索したりしてみてください。
この`Thread`クラスは単純に別のスレッドを作成するだけのものであるため、処理の順序づけや実行後に値を返す処理を実装する際はコードが煩雑になりがちです。
そこで、これらの不便な点を解消し非同期処理を**容易**にするために、`AsyncTask`というクラスがGoogleにより**用意**されています。

`AsyncTask`は、文字通りAsync(非同期 ⇄ 同期: sync)のTask、つまり非同期処理を実装するためのクラスです。
これにより、容易に非同期処理(処理Aを行う前に処理A'を行っておき、処理Aで出た結果を基にUIスレッド(メインスレッド)で処理Bを実行する、などの処理)を実装することができます。
詳しくは、[AsyncTask](https://developer.android.com/reference/android/os/AsyncTask)を参照してみてください。

# 導入方法

では早速、`Coroutines`を実装していきます。
まずappレベルの`build.gradle`にて、依存関係を記述します。

```gradle:build.gradle
dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3' // 最新版(2020.1.19)
}
```

また、Projectレベルの`buil.gradle`にて、Kotlinのバージョンが最新になっていることを確認します。
最新でなくてもいいと思われますが、バージョンによっては`Coroutines`を使用できない場合があります。
`Coroutines`を使用できない場合は、`sync`時やファイルにて各種`Coroutines`のコンポーネントを`import`するときなどでエラーが出ると思われます(知らんけど)。指定のバージョンに設定するか最新バージョンに書き換え、`sync`してみましょう。

```gradle:build.gradle
buildscript {
    ext.kotlin_version = '1.3.61' // 最新版(2020.1.19)
}
```

# 各種キーワード
`Coroutines`で提供されているコンポーネントをキーワードごとにまとめます。

## CoroutineScope
[CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html)は、すべてのCoroutine Builder(後述する`launch`など)の土台となるインターフェースです。
この`CoroutineScope`を継承したスコープ(GlobalScopeなど)を用いて非同期処理を実装することになります。
例：

```kotlin
fun launchTest() {
    println("処理1")
    GlobalScope.launch {
        println("処理2")  // Thread.sleep(1000)の間に実行される
    }
    Thread.sleep(1000) // 実行中の全スレッドを1秒止める
    println("処理3")
}
```

結果：

```
処理1
処理2
処理3
```

`Thread.sleep(1000)`を挿入しているのは、`GlobalScope.launch`内の処理が完了してから`処理3`を実行・出力させるためです。
このようにスコープ内の処理が完了するまでスコープ外の処理を待機させることはよくあることです。
今回は`Thread.sleep(1000)`により、運良く1000(ms)の内に`GlobalScope.launch`内の処理が完了しましたが、全ての場合でそうなるとは限りません。
また、無駄な時間も発生するためこの実装は完全ではありません。

でも安心してください。
`Coroutines`では、以下のように完全に処理が完了するのを待ってくれる優しい機能が提供されています！
これらについては後述します。

- runBlocking
- withContext

先に述べておくと、`withContext`はsuspending function(後述します。後述が多くてすみません! )であるため、coroutine内(例えば、`GlobalScope.launch{ //ここ}`)からしか呼ぶことができません。そんな彼らは、coroutine内で処理を止めたいときに使用されます。

(
少し余談ですが`CoroutineScope`は`Activity`や`ViewModel`などの明確なLife Cycleを持ったクラス内で実装されるべきです。なぜなら、coroutineをキャンセルするタイミングを明確にすることができるからです。~~そのタイミングは親のクラスが破棄されるタイミング、`Activity`であれば`onDestroy`メソッドなどでよいでしょう。`(CoroutineScope).cancel()`を呼び出しておけば問題ないと思われます。~~ おそらくですが、
)

さて、ここで"後述"で先延ばしにしていた説明を行うことにします。

## launch
[launch](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html)は、実行中のスレッドを止めない新たなcoroutineを作成するビルダーで、`Job`クラスを返すことができます。

`launch`は先ほどの例のように、何らかの`CoroutineScope`(例では`GlobalScope`)を基に生成され、ラムダ式(`{}`)内の処理が1つのcoroutineとして実行されます。

`Job`クラスを返すとはどういうことかというと、`launch`を`Job`クラスでインスタンス化したり、`Job`が提供する`cancel`や`join`などのメソッドを使用したりできるということです。
また、インスタンス化することで可読性の向上に繋がる可能性もあります(`Coroutines`では軽量な処理が実行されるべきなので、そもそも煩雑な処理は実行しないようにするのが望ましいと思われますが)。

## Job
[Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html)とは、ライフサイクルを持つbackground "job"のことで、`Job`が完了したときや`cancel()`が呼び出されたときにキャンセルされます。
`launch`などで生成されます。

ここで、`Job`の4つのメソッドの内2つを紹介します。

### join()
`Job`が完了するまでcoroutineを一時中断してくれる、suspending function(後述)です。
例：

```kotlin
fun launchTest {
    GlobalScope.launch {
        val job = launch {
            println("あ〜あ、ちょっと待ってよぉ〜")
        }
        job.join()
        println("待ったよ!")
    }
    Thread.sleep(1000) // 実行中の全スレッドを1秒止める
    println("終わり")
}
```
結果：

```
あ〜あ、ちょっと待ってよぉ〜
待ったよ!
終わり
```

### cancel()
`cancel`を呼び出すことで`Job`をキャンセルすることができます。
問題があった場合は、エラーメッセージが出力されます。
例：

```kotlin
GlobalScope.launch {
    val job = launch {
        for (index in 1..10) {
            // 1秒ごとに"あ〜あ、ちょっと待ってよぉ〜"と歌う
            delay(1000)
            println("あ〜あ、ちょっと待ってよぉ〜") 
        }
    }
    // 3秒以上は待てない
    delay(3000)
    job.cancel()
    println("待ちくたびれたよ!")
}
```

結果：

```
あ〜あ、ちょっと待ってよぉ〜
あ〜あ、ちょっと待ってよぉ〜
あ〜あ、ちょっと待ってよぉ〜
待ちくたびれたよ!
```

全文：

```kotlin
fun launchTest() {
    println("始め!")
    GlobalScope.launch {
        val job = launch {
            for (index in 1..10) {
                // 1秒ごとに"あ〜あ、ちょっと待ってよぉ〜"と歌う
                delay(1000)
                println("あ〜あ、ちょっと待ってよぉ〜") 
            }
        }
        // 3秒以上は待てない
        delay(3000)
        job.cancel()
        println("待ちくたびれたよ!")
    }
    Thread.sleep(10000)
    println("終わり!")
}
```

## 処理を一時中断する

Kotlinでは非同期処理実装のため、suspending functionという処理をsuspendする(一時停止する)メソッドが提供されています。

また、実行中の処理を一時中断するための機構は以下の2つがあります。

- runBlocking
- withContext

`runBlocking`は、UIスレッドなど実行中のすべての処理を一時中断するために、
`withContext`は、coroutines内の処理を一時中断するために使用されます。
それらの詳細を以下で説明します。

### suspending function

suspending functionとは、このメソッド内の処理が終わるまで実行中の処理を一時停止させることができるようなメソッドです。
`Kotlin`独自のメソッドのタイプで、`Coroutines`を実装する際に使用されます。
`suspend fun`と記述することで定義できます。

一時停止するとはいえ、なんでもかんでも停止できるわけではなく、coroutine内においてのみ停止できます。
そのため、`suspend fun`で定義されたメソッドはcoroutine内(`GlobalScope.launch{ //ここ}`など)からのみ呼び出すことができます。

### runBlocking

`runBlocking`とは、その名の通り実行中の処理を止める機能です。
ラムダ内の処理が完了するまで、現在実行中のスレッド(coroutineによるものではないUIスレッドなど)を一旦止めることができます。

これはcoroutineと他のスレッドとの橋渡し役となるよう設計されており、coroutine内では使用されません。
例：

```kotlin
fun runBlockingTest() = runBlocking {
    println("処理1")
    GlobalScope.launch {
        println("処理2")  // delay(3000)の間に実行される
    }
//    Thread.sleep(1000) // 実行中の全スレッドを1秒止める
    delay(3000)  // coroutine(ここではrunBlocking)内の処理のみ3秒止める
    println("処理3")
}
```
実行例：

```kotlin
println("runBlocking、始めるで〜")
runBlockingTest()
println("runBlocking、終わったで〜")
```
結果：

```
runBlocking、始めるで〜
処理1
処理2
処理3
runBlocking、終わったで〜
```

`runBlocking`内の処理が完了するのをちゃんと待ってくれました!!
優しい!

### withContext
`withContext`は、coroutine内で`CoroutineContext`というContextを新たに提供し、この`withContext`内の処理が完了するまでcoroutine内の処理を一時中断する機能が備わっています。
言うなれば、with "Another" Contextですかね。

（
引数として指定する`CoroutineContext`は、[CoroutineDispatcher](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html)を用いて提供されます。
この`CoroutineDispatcher`により、以下3種類の`CoroutineContext`を実装することができます。

- Dispatchers.Default：標準の`CoroutineContext`が提供される。バックグラウンド処理で共通のスレッド・プールを使用するため、CPUを消費するような計算処理を含む場合に最適です。
- Dispatchers.IO：I/Oの処理を中断させたい場合に使われます。
- Dispatchers.Unconfined：あまり使われるべきではないようです。

）

先ほどの`runBlockingTest`内の`delay`メソッドを`withContext`で置き換えた例を記載します。

例：

```kotlin
    @Test
    fun runBlockingTest() = runBlocking {
        println("処理1")
        withContext(Dispatchers.Default) {
            println("処理2")  // Thread.sleep(1000)の間に実行される
        }
//    Thread.sleep(1000) // 実行中の全スレッドを1秒止める
//    delay(3000)  // coroutine(ここではrunBlocking)内の処理のみ3秒止める
        println("処理3")
    }
```
実行例：

```kotlin
println("runBlocking、始めるで〜")
runBlockingTest()
println("runBlocking、終わったで〜")
```
結果：

```
runBlocking、始めるで〜
処理1
処理2
処理3
runBlocking、終わったで〜
```

この通り、`runBlockingTest`内の処理(処理1, 2, 3)が順番通りに実行されました!!

# まとめ
簡単に概要をまとめます。
`Coroutines`とは、軽量なスレッドを提供するKotlin独自の非同期処理ライブラリです。
ラムダ式一つでスレッドを用意し、非同期処理を実装することができます。
`runBlocking`や`withContext`など、実行中のスレッドを一時中断するための機能も簡単に実装することができます。
非同期処理を簡潔に安全に実装できる便利な機構ではないでしょうか。

今回は、Kotlinでの非同期処理で最適と思われる`Coroutines`について記事を書きました。
この情報量でおおよその機能や性質を理解し使用することができるのではないか、と思います。
知らんけど。

### 参考資料
- [Coroutines](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html)：JetBrains公式ガイド
- [coroutines](https://github.com/kotlin/kotlinx.coroutines/blob/master/README.md#using-in-your-projects)：JetBrains公式サンプルプロジェクト
- [Thread](https://www.google.com/url?client=internal-element-cse&cx=000521750095050289010:zpcpi1ea4s8&q=https://developer.android.com/reference/java/lang/Thread&sa=U&ved=2ahUKEwiRz6HEk4_nAhXPwYsBHUuMBykQFjAAegQIABAB&usg=AOvVaw1nMIY5mjbD8ncNqPvjTXEK)：Google公式リファレンス
- [AsyncTask](https://developer.android.com/reference/android/os/AsyncTask)：Google公式リファレンス
- [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html)：JetBrains公式リファレンス
- [launch](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html)：JetBrains公式リファレンス
- [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html)：JetBrains公式リファレンス
- [CoroutineDispatcher](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html)：JetBrains公式リファレンス