KotlinとCoroutinesでAyncTask

# はじめに
以前、Coroutinesが実験段階の時にAsyncTaskと同等の機能をCoroutinesで実装する記事を上げました。（記事は[こちら](https://qiita.com/naoi/items/9db5e58d4f47d25a19d5)）
Coroutinesが正式にリリースされて約１年が経とうとしているので、正式版のCoroutinesでAsyncTaskと同等の機能を実装方法を記事にまとめて見ようと考えました。
今回は、キャンセル処理もしっかり載せております。
記事の構成は、以前の[内容](https://qiita.com/naoi/items/9db5e58d4f47d25a19d5)を同じようにしております。
見比べながらこの時期を読むと、実験段階との差がはっきりします。

# 動作環境
この記事の動作環境は以下のとおりです。

Android Studio：3.5
Kotln：1.3.50
Open JDK:1.8
compileSdkVersion：29
targetSdkVersion：29
minSdkVersion：23
Coroutines:1.3.2


# AsyncTaskとは
AsyncTaskは、Androidで非同期処理の実装に便利なユーティリティクラスの1つです。
詳細は、下記のサイトを参照してください。
（https://developer.android.com/reference/android/os/AsyncTask.html）

# Coroutinesとは
Coroutinesとは、Kotlin 1.3から正規にに導入された非同期処理を簡単に実装できる機能です。
async/await/launchなどの関数利用して、非同期処理を実現します。
関数の詳細については、[公式サイト](https://kotlinlang.org/docs/reference/coroutines-overview.html)を参照してください。
AsyncTaskと同等の機能をCoroutinesで実装した時の差を確認するため、本記事でCoroutine詳細については省略します。

#サンプルアプリ
## 完成イメージ
この記事で作成するサンプルアプリの完成イメージは、下図のとおりです。
![01_アプリイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/b41dccc5-8e03-b865-a8e5-2e6e9ac00f7e.png)


## サンプルアプリの詳細
スタートボタンを押下すると、TextViewの表示が「始めます」に変わります。その後、TextViewに1〜10の値が0.8秒間隔でカウントアップします。カウントアップが終わると、TextViewの表示は「終わります」に変わります。

# ソースコードと解説
サンプルアプリは、以下のファイルで構成されています。
（プロジェクト生成後から変更がないマニフェストファイルなどは、省略しています。）
## ファイル一覧
### レイアウト
* activity_main.xml

### Activity
* MainActivity.kt（AsyncTaskで実装）
* MainActivity.kt（Coroutinesで実装）

### Gradle
* build.gradle(app)

## Gradle
AndroidでCoroutineを利用するため、build.gradle(app)のdependenciesに下記の2行を追記します。

```groovy
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
```

## レイアウト
最終的なレイアウトのイメージは、下図のとおりです。

![02_レイアウト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/c50e9b7b-1e4d-3b55-62f0-5c4ae37a194d.png)




### activity_main.xml
画面全体のレイアウトファイルの内容は、以下のとおりです。

```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="jp.co.casareal.afterasync.MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="30sp" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="スタート"
        android:textSize="30sp" />
</LinearLayout>
```

## Activity

### MainActivity.kt（AsyncTaskで実装）
AsyncTaskでサンプルアプリを実装した時のアクティビティのソースコードは、以下のとおりです。

```kotlin:MainActivity
package jp.co.casareal.asyncbefor

import android.os.AsyncTask
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            MyAsyncTask().execute()
        }
    }

    inner class MyAsyncTask : AsyncTask<Void, Int, Void>() {

        override fun onPreExecute() {
            text.setText("始めます")
            Thread.sleep(800)
        }

        override fun doInBackground(vararg param: Void?): Void? {
            for (i in 1..10) {
                publishProgress(i)
                Thread.sleep(800)
            }
            return null
        }

        override fun onProgressUpdate(vararg values: Int?) {
            text.setText(values[0].toString())
        }

        override fun onPostExecute(result: Void?) {
            text.setText("終わります")
        }

    }
}
```
#### 実装の解説
AsyncTaskを継承したクラスを、インナークラスとして定義しています。
onPreExecuteメソッド、onProgressUpdateメソッド、onPostExecuteメソッドは、UIの変更できるmainスレッドで実行されます。その為、サンプルコードでは、TextViewのプロパティを変更しています。


### MainActivity.kt（Coroutineで実装）

```kotlin
package jp.co.casareal.kotlinasynccoroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity() {

    val scope = CoroutineScope(Dispatchers.Default)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()
        button.setOnClickListener {

            scope.launch {
                myTask()
            }
        }
    }

    override fun onPause() {
        super.onPause()
        scope.coroutineContext.cancelChildren()
    }


    private suspend fun myTask() {
        try {

            // onPreExecuteと同等の処理
            withContext(Dispatchers.Main) {
                text.text = "始めます"
            }

            // doInBackgroundメソッドと同等の処理
            Thread.sleep(800)
            for (i in 1..10) {
                //onProgressUpdateメソッドと同等の処理
                withContext(Dispatchers.Main) {
                    text.text = i.toString()
                }
                Thread.sleep(800)
            }

            // onPostExecuteメソッドと同等の処理
            withContext(Dispatchers.Main) {
                text.text = "終わります"
            }
        } catch (e: Exception) {
            // onCancelledメソッドと同等の処理
            Log.e(localClassName, "ここにキャンセル時の処理を記述", e)
        }
    }
}
```

#### 実装の解説
非同期処理を実装するために、launch関数を利用しています。launch関数の定義は[公式サイト](https://kotlinlang.org/docs/reference/coroutines/basics.html#coroutines-are-light-weight)を参照してください。

サンプルコードでは、下記の2種類の関数を使用します。

* launch
* withContext

asyncを使用しない理由としては、UIの更新を行うためにmainスレッドでの実行が必要になります。
その際、Coroutineのスレッドを変更して、UI更新処理が完了するまで、親コルーチンを中断する必要があります。
もし、asyncでやるとなるとawaitで中断する方法をとります。しかし、この様な場合は、withContext関数を利用して、Contextを切り替える方が適切です。
そのため、本記事ではwithContextを利用しています。

##### Scopeについて

Coroutinesを実行させるには、どのスコープで実行させるかというのが必要になります。
Coroutinesを理解する上で、多少なりとも「Scope」と「Context」と「Job」について理解しておか泣かないとこのサンプルコードを理解出来ません。また、キャンセルさせるためにも、Contextが必要となります。

この３つがどの様な位置にあるかを図にしてみました。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/9c0d4dc7-407d-ed2a-9502-1a0d09b510e0.png)


図の通りですが、Scopeの中にContextが存在し、そのContextの中にJobが存在します。
キャンセルする場合は、Contextをキャンセルするとその配下にあるJob全てをキャンセル出来ます。

また、CoroutineScopeは複数種類あります。

* CoroutineScope
* GlobalScope

GlobalScopeはAndroidで推奨さていないので、CoroutineScopeを利用します。
GlobalScopreが推奨されていない理由はご自身でお調べください。

さらにCoroutineScopeの実行するスレッドを引数で選択します。

```kotlin
val scope = CoroutineScope(Dispatchers.Default)
```

実装例では、上記の部分になります。

４つの種類がKotlinでは用意されています。

|種類|説明|
|:--|:--|
|Dispatchers.Main|Mainスレッド（UIスレッド）で実行|
|Dispatchers.Default|バックグラウンドの共有プールを利用する|
|Dispatchers.IO|追加でスレッドを立てる。|
| Dispatchers.Unconfined |基本的には使用しないように説明がKotlinで書かれています。|

今回はCoroutineScopeで実行させます。

##### launch関数

```kotlin
scope.launch {
　// 処理
}
```
上記の方法で記述すると、「*mainスレッドではないスレッド*」で処理が実行されます。その為、UIの変更は出来ません。

##### withContext(Dispatchers.Main)関数
withContext関数はCorouineのContextを切り替える事ができます。
UIを変更するのであればMainスレッドに切り替える必要があります。引数に切り替えたいスレッドを指定します。
以下の様な実装になります。

```kotlin
withContext(Dispatchers.Main) {
    // UIの更新
}
```

##### suspend修飾子

コルーチンから呼び出すメソッドや関数は、「suspend」修飾子を必ず付与しなければなりません。

```kotlin
private suspend fun myTask() {・・・処理・・・}
```

##### キャンセル処理

Coroutineをキャンセルするには、安全性を確保するために全てのJobをキャンセルする必要があります。
全てのJobをキャンセルするにはContextを使ってキャンセルします。ContextをキャンセルするとそのContextに保持されているJobは全てキャンセルされます。また、Jobの中で生成した子ルーチンも全てキャンセルされます。

```kotlin
override fun onPause() {
    super.onPause()
    scope.coroutineContext.cancel()
}
```

キャンセルするとコルーチンないで*JobCancellationException*が発生します。もし、キャンセルの際に何かしらの処理を行いたい場合は、catch句で終了処理を記述します。

```kotlin
    private suspend fun myTask() {
        try {
　　　〜〜〜省略〜〜〜
        } catch (e: Exception) {
            // onCancelledメソッドと同等の処理
            Log.e(localClassName, "ここにキャンセル時の処理を記述", e)
        }

```

# まとめ
実験段階の実装方法と大きく異る部分もありますが、基本的な思想は変わっていないように感じます。
AndroidでCoroutineを使用する時はThreadやScope、Contextに注意が必要です。