同期処理と非同期処理
同期処理
同じスレッド
上で行われる処理。
同期処理
のメソッドを呼び出した場合、
呼び出したメソッドの処理が終わるまで呼び出し元メソッド
は待機状態
となる。
Kotlin
/Java
では、基本的にメイン処理が同期処理
で行われる。
スレッド
処理の一連の流れの単位
。
Androidアプリ
では、アクティビティ
を処理するUIスレッド
と非同期処理
を行うワーカースレッド
に分類される。
UIスレッド
アクティビティ
が実行されるメインスレッド
。
ワーカースレッド
UIスレッド
とは異なるスレッドで非同期処理
を行うスレッド
。
非同期処理
異なるスレッド
上で行われるマルチスレッド処理
。
非同期処理
のメソッドを呼び出した場合、
呼び出したメソッドの処理の終了を待たずに呼び出し元メソッド
は続きの処理を実行するため、
時間がかかる処理は非同期処理
で行うのが一般的。
Executorを利用した非同期処理
参考1: バックグラウンドスレッドでのAndroidタスクの実行
参考2: ExecutorService
参考3: Thread, Looper, Handler
Executor
を用いた非同期処理
を実装する手順は、以下の通り。
Executors
クラスメソッドを用いてワーカースレッド
を生成し、スレッドプール
に追加ワーカースレッド
での処理を終えた後にUIスレッド
で処理を続行する場合は、HandlerCompat
クラスメソッドを用いてHandler
オブジェクトを生成Runnable
インタフェースを実装したクラスを作成し、run()
メソッドをオーバーライド
して自動的に実行される処理を記述スレッドプール
に、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を利用した非同期処理の実装
スレッドプールへのスレッド追加
定義
// 1スレッドの追加
Executors.newSingleThreadExecutor(): ExecutorService
// 指定したスレッド数のスレッドプールに変更
Executors.newFixedThreadPool(nThreads: Int): ExecutorService
// パラメータ
// nThreads: スレッド数
// 再利用可能なスレッドを必要数に応じて追加
Executors.newCachedThreadPool(): ExecutorService
// 指定したスレッド数をもち指定時間おきに処理するスレッドプールを追加
Executors.newScheduledThreadPool(corePoolSize: Int): ScheduledExecutorService
// パラメータ
// corePoolSize: スレッド数
サンプルコード
// 1スレッド追加したスレッドプールをもつExecutorServiceプロパティ
val executeService = Executors.newSingleThreadExecutor()
Handlerオブジェクトの生成
定義
HandlerCompat.createAsync(@NonNull looper: Looper): Handler
// パラメータ
// looper: HandlerにバインドするLooper
サンプルコード
// 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
オブジェクトを受け取る場合は、コンストラクタ
に組み込む必要がある。
サンプルコード
// 非同期処理を行う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)
}
}
// ワーカースレッドでの処理後に、
// UIスレッドで動作するRunnableオブジェクトを定義するクラス
private inner class WeatherInfoPostExecutor(result: String) : Runnable {
...
// Handlerによって自動的に実行される処理
// -> UIスレッドで動作することを明示的に記述(@UiThreadアノテーション)
@UiThread
override fun run() {
... // 処理内容
}
}
スレッドプールに処理を送信
定義
ExecutorService.submit(task: Runnable): Future<?>
// パラメータ
// task: スレッドプールに送信するRunnableオブジェクト
サンプルコード
// 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接続
を行う手順は、以下の通り。
AndroidManifest.xml
に、HTTP接続
を許可するパーミッション
タグを記述接続先URL
を定義したURL
オブジェクトをHttpURLConnection
オブジェクトに変換HTTP接続
に関する設定を記述し、HTTP接続
を実行InputStream
型のレスポンスデータ
をString型
に変換JSON
データに変換したレスポンスデータ
の解析(=JSON解析
)HttpURLConnection
オブジェクトを解放
マニフェストファイルでHTTP接続を許可
参考1: マニフェストファイルの概要
参考2: ネットワークへの接続
参考3: 研修1日目
アプリ
の基本情報を記述するマニフェストファイル
に、HTTP接続
を許可するパーミッション
を記述する。
サンプルコード
<?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接続
を実行するメソッド
を定義するクラス
。
定義
URL.openConnection(): URLConnection
サンプルコード
// 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接続
がうまくできなかった場合は例外
が発生するため、例外処理
を用いて記述する必要がある。
定義
URLConnection.connect()
// Throws:
// SocketTimeoutException: タイムアウトに関する例外
// IOException: 接続エラーに関する例外
サンプルコード
// 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
バッファリング
を行うことで読み込み
の高速化を実現しながら、
読み込んだデータ
をオブジェクトとして保持できるクラス。
定義
// InputStreamオブジェクトの解放
InputStream.close()
サンプルコード(InputStream→Stringへの変換)
// 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オブジェクトの解放)
// 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
によって記述された、階層構造
をもつデータ。
// JSONObject: {...}で囲まれたキー付きのマップ
{
"<name>":{
"<name>":<value>,
"<name>":<value>,
},
// JSONArray: [...]で囲まれたキー付きのリスト
"<name>":[
{
"<name>":<value>,
"<name>":<value>,
},
],
...
"<name>":<value>,
"<name>":<value>
}
定義
/*
いずれのメソッドも、条件に一致する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
型に変換したリクエストパラメータ
を送信する。
サンプルコード
// 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()
}