【Kotlin研修8日目】Executorを利用した非同期処理の実装、Web APIを利用したデータ取得およびJSON解析

# 同期処理と非同期処理

## 同期処理
同じ`スレッド`上で行われる処理。

`同期処理`のメソッドを呼び出した場合、
呼び出したメソッドの処理が終わるまで`呼び出し元メソッド`は`待機状態`となる。

`Kotlin`/`Java`では、基本的にメイン処理が`同期処理`で行われる。

### スレッド
処理の一連の流れの`単位`。
`Androidアプリ`では、`アクティビティ`を処理する`UIスレッド`と`非同期処理`を行う`ワーカースレッド`に分類される。

#### UIスレッド
`アクティビティ`が実行されるメイン`スレッド`。

#### ワーカースレッド
`UIスレッド`とは異なるスレッドで`非同期処理`を行う`スレッド`。

## 非同期処理
異なる`スレッド`上で行われる`マルチスレッド処理`。

`非同期処理`のメソッドを呼び出した場合、
呼び出したメソッドの処理の終了を待たずに`呼び出し元メソッド`は続きの処理を実行するため、
時間がかかる処理は`非同期処理`で行うのが一般的。

***

# Executorを利用した非同期処理
[参考1: バックグラウンドスレッドでのAndroidタスクの実行](https://developer.android.com/guide/background/threading?hl=ja)
[参考2: ExecutorService](https://java.keicode.com/lang/multithreading-executor.php)
[参考3: Thread, Looper, Handler](https://academy.realm.io/jp/posts/android-thread-looper-handler/)
`Executor`を用いた`非同期処理`を実装する手順は、以下の通り。

> 1. `Executors`クラスメソッドを用いて`ワーカースレッド`を生成し、`スレッドプール`に追加
> 2. `ワーカースレッド`での処理を終えた後に`UIスレッド`で処理を続行する場合は、`HandlerCompat`クラスメソッドを用いて`Handler`オブジェクトを生成
> 3. `Runnable`インタフェースを実装したクラスを作成し、`run()`メソッドを`オーバーライド`して自動的に実行される処理を記述
> 4. `スレッドプール`に、3.で作成したクラスのオブジェクト(=`Runnable`オブジェクト)を送信

## Executorインタフェース
`ワーカースレッド`の生成や`ワーカースレッド`での処理を実行するメソッドを抽象的に定義した`インタフェース`。

## Executorsクラス
`ExecutorService`の`ファクトリクラス`。

## ExecutorServiceインタフェース
`Executor`インタフェースの抽象メソッドを**拡張**しており、
`スレッドプール`の**作成**や`スレッドプール`への`Runnableタスク`の**送信**など、
`スレッドプール`を管理するメソッドを抽象的に定義した`インタフェース`。

### スレッドプール
複数の`スレッド`を待機させ、処理する`Runnable`オブジェクト(=`Runnableタスク`)が到着すると、
待機状態の`スレッド`に`Runnableタスク`を割り当て、処理を自動的に開始させる仕組み。

### ファクトリ(クラス)
「`インスタンス`(=`オブジェクト`)の**生成**」を目的に作られた`クラス`。

## Runnableインタフェース
特定の`スレッド`内での処理を自動的に実行する`run()`メソッドを抽象的に定義した`インタフェース`。

## Handler
`Looper`オブジェクトを保持し、`Looper`から命令を受けると`メッセージキュー`内の処理を実行するクラス。

`Looper`オブジェクトを保持していることから、
実質的に特定の`スレッド`内での処理を`管理`・`実行`する。

`UIスレッド`で作成した`Handler`オブジェクトを`ワーカースレッド`の`Runnableオブジェクト`に渡し、
`ワーカースレッド`側で`UIスレッド`で動作させる処理(=`Runnableオブジェクト`)を、
`Handler`がもつ`Looper`オブジェクトに送信(=`post()`)してもらうことで、
`ワーカースレッド`での処理を終えた後、`UIスレッド`での処理続行が可能になる。

このことから、`Handler`は「スレッド間の**通信**を行うクラス」とも言われる。

## Looper
特定の`スレッド`内での処理(=`Runnable`オブジェクト)を`先入先出法`で管理し、
監視している`メッセージキュー`に処理がある限り、`Handler`に実行するよう命令するクラス。

***

# Executorを利用した非同期処理の実装

## スレッドプールへのスレッド追加

### 定義

```kotlin
// 1スレッドの追加
Executors.newSingleThreadExecutor(): ExecutorService

// 指定したスレッド数のスレッドプールに変更
Executors.newFixedThreadPool(nThreads: Int): ExecutorService
// パラメータ
// nThreads: スレッド数

// 再利用可能なスレッドを必要数に応じて追加
Executors.newCachedThreadPool(): ExecutorService

// 指定したスレッド数をもち指定時間おきに処理するスレッドプールを追加
Executors.newScheduledThreadPool(corePoolSize: Int): ScheduledExecutorService
// パラメータ
// corePoolSize: スレッド数
```

### サンプルコード

```kotlin:MainActivity.kt
// 1スレッド追加したスレッドプールをもつExecutorServiceプロパティ
val executeService = Executors.newSingleThreadExecutor()
```

## Handlerオブジェクトの生成

### 定義

```kotlin
HandlerCompat.createAsync(@NonNull looper: Looper): Handler
// パラメータ
// looper: HandlerにバインドするLooper
```

### サンプルコード

```kotlin:MainActivity.kt
// UIスレッドで最初に実行する処理
// -> UIスレッドで動作することを明示的に記述(@UiThread)
@UiThread
private fun receiveWeatherInfo(urlFull: String) {
    // UIスレッドで動作する処理を管理するHandlerオブジェクトの生成
    // mainLooper: アクティビティ(=UIスレッド)がもつLooperオブジェクト
    val handler = HandlerCompat.createAsync(mainLooper)
    ...
}
```

## Runnableオブジェクトの定義
`Runnable`インタフェースは「`スレッドプール`や`Handler`によって自動実行される処理」を**抽象的に定義**した`run()`メソッドをもつため、
`Runnable`インタフェースを実装するクラスでは`オーバーライド`して処理を記述する。
その際、動作する`スレッド`を`アノテーション`で記述することで、指定した`スレッド`での動作が保証される。

また、`UIスレッド`から`Handler`オブジェクトを受け取る場合は、`コンストラクタ`に組み込む必要がある。

### サンプルコード

```kotlin
// 非同期処理を行うRunnableオブジェクトを定義するクラス
private inner class WeatherInfoBackgroundReceiver(handler: Handler, url: String): Runnable {
    // クラス内で扱うHandlerオブジェクト(=Handlerプロパティ)
    // -> クラス内での書き換えが発生しないよう、スレッドセーフ(読み込み専用のval)で定義
    private val _handler = handler
    ...
    // スレッドプールによって自動的に実行する処理
    // -> ワーカースレッドで動作することを明示的に記述(@WorkerThreadアノテーション)
    @WorkerThread
    override fun run() {

        ...   // 処理内容

        // 非同期処理の終了後にUIスレッドで行う処理(Runnableオブジェクト)
        val postExecutor = WeatherInfoPostExecutor(result)

        // UIスレッドでの続行処理をUIスレッドHandlerのLooperに送信
        _handler.post(postExecutor)
    }
}
```

```kotlin:MainActivity.kt
// ワーカースレッドでの処理後に、
// UIスレッドで動作するRunnableオブジェクトを定義するクラス
private inner class WeatherInfoPostExecutor(result: String) : Runnable {
    ...
    // Handlerによって自動的に実行される処理
    // -> UIスレッドで動作することを明示的に記述(@UiThreadアノテーション)
    @UiThread
    override fun run() {
        ...   // 処理内容
    }
}
```

## スレッドプールに処理を送信

### 定義

```kotlin
ExecutorService.submit(task: Runnable): Future<?>
// パラメータ
// task: スレッドプールに送信するRunnableオブジェクト
```

### サンプルコード

```kotlin:MainActivity.kt
// UIスレッドで最初に実行する処理
// -> UIスレッドで動作することを明示的に記述(@UiThread)
@UiThread
private fun receiveWeatherInfo(urlFull: String) {
    // スレッド数2のスレッドプールを管理するExecutorServiceプロパティ
    val executeService = Executors.newSingleThreadExecutor()

    // UIスレッドで動作する処理を管理するHandlerオブジェクトの生成
    // mainLooper: アクティビティ(=UIスレッド)がもつLooperオブジェクト
    val handler = HandlerCompat.createAsync(mainLooper)

    // ワーカースレッドで処理するRunnableオブジェクト
    val backgroundReceiver = WeatherInfoBackgroundReceiver(handler, urlFull)

    // スレッドプールにRunnableオブジェクトを送信
    // -> 非同期処理の開始
    executeService.submit(backgroundReceiver)
}
```

***

# AndroidのWeb連携
`Android`端末からインターネット上の`データベース`にアクセスする場合、
`DB`と直接データのやり取りを行う`サーバーサイドWebアプリ`(=`Web API`)を通じて`DB`にアクセスする。

# Web APIを利用したデータ取得
`Web API`にアクセスするためには、`API`を利用するための`APIキー`が必要となる。
また、`Web API`の利用にあたって`HttpURLConnection`クラスを用いて`HTTP接続`を行う必要がある。

`HTTP接続`を行う手順は、以下の通り。

> 1. `AndroidManifest.xml`に、`HTTP接続`を許可する`パーミッション`タグを記述
> 2. `接続先URL`を定義した`URL`オブジェクトを`HttpURLConnection`オブジェクトに変換
> 3. `HTTP接続`に関する設定を記述し、`HTTP接続`を実行
> 4. `InputStream`型の`レスポンスデータ`を`String型`に変換
> 5. `JSON`データに変換した`レスポンスデータ`の解析(=`JSON解析`)
> 6. `HttpURLConnection`オブジェクトを解放

## マニフェストファイルでHTTP接続を許可
[参考1: マニフェストファイルの概要](https://developer.android.com/guide/topics/manifest/manifest-intro?hl=ja)
[参考2: ネットワークへの接続](https://developer.android.com/training/basics/network-ops/connecting)
[参考3: 研修1日目](https://qiita.com/b150005/items/46ffc9a4d079cccdc807)
`アプリ`の基本情報を記述する`マニフェストファイル`に、`HTTP接続`を許可する`パーミッション`を記述する。

### サンプルコード

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <uses-permission
        android:name="android.permission.INTERNET"
    />

    <uses-permission
        android:name="android.permission.ACCESS_NETWORK_STATE"
    />
    ...
</manifest>
```

## URLオブジェクトをHttpURLConnectionオブジェクトに変換
`接続先URL`の文字列から生成した`URL`オブジェクトを、`HttpURLConnection`オブジェクトに**変換**する。

### HttpURLConnection
`HTTP接続`に関する`設定値`(=`プロパティ`)をもち、`HTTP接続`を実行する`メソッド`を定義する`クラス`。

### 定義

```kotlin
URL.openConnection(): URLConnection
```

### サンプルコード

```kotlin:MainActivity.kt
// String型 → URL型への変換
val url = URL("<URL文字列>")

// URL型 → URLConnection型 → HttpURLConnection型への変換
val con = url.openConnection() as? HttpURLConnection
```

## HTTP接続設定の定義・実行

### HTTP接続に関する設定

#### (Http)URLConnectionの主なプロパティ

|プロパティ|内容|
|:--|:--|
|`connectTimeout`|HTTP接続がタイムアウトするまでの時間[ms]|
|`readTimeout`|データ取得がタイムアウトするまでの時間[ms]|
|`requestMethod`|HTTPリクエストのメソッド|
|`responseCode`|HTTPステータスコード|

### HTTP接続の実行
`HTTP接続`がうまくできなかった場合は`例外`が発生するため、`例外処理`を用いて記述する必要がある。

#### 定義

```kotlin
URLConnection.connect()
// Throws:
// SocketTimeoutException: タイムアウトに関する例外
// IOException: 接続エラーに関する例外
```

#### サンプルコード

```kotlin:MainActivity.kt
// URLオブジェクト
val url = URL("<URL文字列>")

// HttpURLConnectionオブジェクト
val con = url.openConnection() as? HttpURLConnection

// HttpURLConnectionオブジェクトがnullでないことを保証
// -> let関数ブロック内では、nullチェック対象(=con)がitで置換
con?.let {

    // 例外が発生する可能性があるブロック
    try {
    // -- HTTP接続設定の定義開始 --
        // 接続がタイムアウトするまでの時間[ミリ秒]
        // -> タイムアウトした場合はSocketTimeoutExceptionが発生
        it.connectTimeout = 1000

        // データ取得がタイムアウトするまでの時間[ミリ秒]
        // -> タイムアウトした場合はSocketTimeoutExceptionが発生
        it.readTimeout = 1000

        // HTTP接続メソッドの指定
        it.requestMethod = "GET"
    // -- HTTP接続設定の定義終了 --

        // HTTP接続の実行
        // ->場合によってSocketTimeoutExceptionまたはIOExceptionが発生
        // -> 接続時、HttpURLConnectionオブジェクトのinputStreamプロパティに
        //    InputStream型のレスポンスデータが自動的に格納
        it.connect()
    }
    // 例外が発生した場合のエラー処理
    catch (ex: SocketTimeoutException) {
        ...   // Logcatにログを出力
    }
}
```

## InputStream型→String型の変換
`InputStream`型の`レスポンスデータ`は、`Byteデータ`であるため、`InputStreamReader`や`BufferedReader`を用いて、
`文字データ`として読み込んでから`String`型に変換する必要がある。

また、`String`型に変換した後は`InputStreamReader`を`ラップ`した`BufferedReader`オブジェクトや、`InputStream`オブジェクトを解放することで`リソース効率`を向上させる。

### InputStream
`読み込み専用`の`Byteデータ`を抽象的に定義するクラス。

### InputStreamReader
`InputStream`型の`Byteデータ`を読み込み、様々な`文字コード`に対応しながら`文字データ`に変換し、
変換した`文字データ`をオブジェクトとして保持できるクラス。

### BufferedReader
`バッファリング`を行うことで`読み込み`の高速化を実現しながら、
読み込んだ`データ`をオブジェクトとして保持できるクラス。

### 定義

```kotlin
// InputStreamオブジェクトの解放
InputStream.close()
```

### サンプルコード(InputStream→Stringへの変換)

```kotlin:MainActivity.kt
// InputStream型 → String型 への変換処理
private fun is2String(stream: InputStream?): String {

    // 1行分の文字データを格納するStringBuilderオブジェクト
    // StringBuilder: 文字列を格納する(同一ポインタ内での)可変配列を定義するクラス
    val sb = StringBuilder()

    //　Byteデータ → 文字データ への変換
    val reader = BufferedReader(InputStreamReader(stream, "UTF-8"))

    // 1行分の文字データ
    // -> 1行目の読み込み
    var line = reader.readLine()

    // 最終行までループさせる処理
    while (line != null) {

        // 読み込んだ1行分の文字データをStringBuilderオブジェクトに格納
        sb.append(line)

        // 次の行を読み込む
        line = reader.readLine()
    }

    // BufferedReaderオブジェクトの解放
    reader.close()

    // 読み込んだ文字データ(StringBuilder型)をString型に変換
    return sb.toString()
}
```

### サンプルコード(InputStreamオブジェクトの解放)

```kotlin:MainActivity.kt
// HttpURLConnectionオブジェクトによるレスポンスデータの取得
val stream = <HttpURLConnectionオブジェクト>.inputStream

// InputStream型のレスポンスデータをString型に変換
result = is2String(stream)

// InputStreamオブジェクトの解放
stream.close()
```

## JSON解析によるデータ取得
`HTTP接続`時に`HttpURLConnection`オブジェクトに格納された`Byteデータ`(=`InputStream`)の中身は、
`JSON(JavaScript Object Notation)`で記述された`JSONデータ`であるため、
`JSON解析`によって取得する`データ`を含む`マップ`を抽出し、
最終的に、`get<T>()`メソッドを用いてデータを取得する。

### JSONデータ
`キー`(=`name`)と`値`を保持する`マップ`が、`データ記述言語`である`JSON`によって記述された、`階層構造`をもつデータ。

```js:String型に変換したJSON文字列
// JSONObject: {...}で囲まれたキー付きのマップ
{
    "<name>":{
        "<name>":<value>,
        "<name>":<value>,
    },
// JSONArray: [...]で囲まれたキー付きのリスト
    "<name>":[
        {
            "<name>":<value>,
            "<name>":<value>,
        },
    ],
    ...
    "<name>":<value>,
    "<name>":<value>
}
```

### 定義

```kotlin
/*
    いずれのメソッドも、条件に一致するJSONデータが見つからない場合、
    JSONExceptionが発生
*/

// JSONObjectの生成
val rootJSON = JSONObject(json: String): JSONObject
// パラメータ
// json: JSONによって記述された文字列

// JSONArrayの取得
rootJSON.getJSONArray(name: String): JSONArray
// name: JSONArrayのキー

// JSONObjectに含まれるJSONObjectの取得
rootJSON.getJSONObject(name: String): JSONObject
// name: JSONObjectのキー

// データの取得
rootJSON.get<T>(name: String): <T>
// name: マップのキー
```

***

# WebAPIを利用したデータ格納
`Web API`を通じて`DB`の`データ更新処理`を行う場合は、
`HttpURLConnection`オブジェクトがもつ`OutputStream`オブジェクト(=`outputStreamプロパティ`)を通じて、`ByteArray`型に変換した`リクエストパラメータ`を送信する。

## サンプルコード

```kotlin:MainActivity.kt
// URLオブジェクト
val url = URL("<URL文字列>")

// HttpURLConnectionオブジェクト
val con = url.openConnection() as? HttpURLConnection

// 送信するリクエストパラメータ文字列
val postData = "name=${name}&comment=${comment}"

// HttpURLConnectionオブジェクトがnullでないことを保証
// -> let関数ブロック内では、nullチェック対象(=con)がitで置換
con?.let {

    // 例外が発生する可能性があるブロック
    try {
    // -- HTTP接続設定の定義開始 --
        // 接続がタイムアウトするまでの時間[ミリ秒]
        // -> タイムアウトした場合はSocketTimeoutExceptionが発生
        it.connectTimeout = 1000

        // データ取得がタイムアウトするまでの時間[ミリ秒]
        // -> タイムアウトした場合はSocketTimeoutExceptionが発生
        it.readTimeout = 1000

        // HTTP接続メソッドの指定
        it.requestMethod = "POST"

        // リクエストパラメータの出力を可能にする
        it.doOutput = true
    // -- HTTP接続設定の定義終了 --

        // HTTP接続の実行
        // ->場合によってSocketTimeoutExceptionまたはIOExceptionが発生
        // -> 接続時、HttpURLConnectionオブジェクトのinputStreamプロパティに
        //    InputStream型のレスポンスデータが自動的に格納
        it.connect()

        // OutputStreamオブジェクト
        val outStream = con.outputStream

        // String型のリクエストパラメータをByteArray型に変換し、
        // OutputStreamオブジェクトに書き込む
        // -> 出力エラー時はIOExceptionが発生
        outStream.write(postData.toByteArray())

        // バッファリングされたリクエストパラメータを
        // OutputStreamオブジェクトに強制的に書き込む
        // -> 基本的にはwrite()で書き込みが完了しているが、
        //    万が一に備えてflush()メソッドを記述
        // -> 出力エラー時はIOExceptionが発生
        outStream.flush()

        // OutputStreamオブジェクトの解放
        // -> 実行エラー時はIOExceptionが発生
        outStream.close()
    }
    catch {
        ...   // エラー処理を記述
    }

    // HttpURLConnectionオブジェクトの解放
    it.disconnect()
}
```