CodeZine(コードジン)

特集ページ一覧

KotlinによるAndroidの非同期処理

2020年版Androidの非同期処理 第3回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2021/01/08 11:00

 2020年9月8日にAndroid 11(APIレベル30)がリリースされました。このAPIレベル30で、AsyncTaskクラスが非推奨となりました。AsyncTaskは、Androidの非同期処理を行う際に非常に便利なクラスとして、様々な場面で利用されてきました。このクラスが非推奨になるということは、今後は代替の方法を採用していく必要があります。本稿では、全3回にわたって、非同期処理がよく登場する場面としてWeb API連携を題材に、AsyncTaskクラスを利用しないAndroidの非同期処理を紹介していきます。前回は、HTTPアクセスとJSONデータの処理方法を紹介しました。最終回である今回は、Kotlin言語によるAndroidの非同期処理の記述方法を紹介します。

目次

Kotlinによる非同期処理とKotlinコルーチン

 前回で、非同期で天気情報を取得して表示するアプリが完成しました。今回は、開発言語をKotlinとした場合の話です。Kotlinには、Kotlin独特の非同期処理の方法があります。そのあたりから概観していきましょう。

JavaコードをKotlinコードに置き換えた場合

 Kotlin言語はJVM上で動作する言語ですので、基本的にJava言語とほぼ同様に記述できる言語であり、Javaクラスがそのまま利用できます。ということは、前回までに紹介したExecutorServiceやHandler、Looperといったクラス郡を利用したコードを、そのままKotlinコードに置き換えれば、非同期処理が実現できます。実際、そのようなコーディングを行ったサンプルとして、AsyncKotlinSampleプロジェクトをダウンロードサンプルに含めていますので、参考にしてください。

Kotlinコルーチン

 一方、Kotlinには、Java由来の非同期処理コードとは別の記述方法として、Kotlinコルーチンというのがあります。こちらは、Java言語にはないものです。

 コルーチンは、Kotlin独自のものではなく、1960年代からある考え方で、様々な言語に採用されています。コルーチンの難しい話は別媒体に譲りますが、簡単にいうと、ひとつの非同期処理ブロック内で、処理を途中で中断して、その中断している間に別の処理を実行することができる仕組みです。もちろん、中断した処理は、その後適切なタイミングで再開させることができます。

 例えば、HTTPアクセスを行う処理が記述されたbackgroundTaskRunner()メソッドとJSON解析と画面表示処理を行うpostExecutorRunner()メソッドがあるとします。これらは、それぞれ前回のリスト6で記述したBackgroundTaskクラスのrun()メソッド内の記述、および、リスト8で記述したPostExecutorクラスのrun()メソッド内の記述をKotlinに置き換えたものを想定してください。

 これら両メソッド内の処理というのは、それぞれワーカースレッドとUIスレッドで動作しますが、その両方ともが非同期処理である必要があります。しかも、backgroundTaskRunner()の処理が終了した後にpostExecutorRunner()が実行される必要があります。でないと、postExecutorRunner()ではbackgroundTaskRunner()の実行結果であるJSON文字列を受け取ることができず、JSON解析が行えません。

 このような場合、backgroundTaskRunner()とpostExecutorRunner()をひとつの非同期処理ブロックとした上で、backgroundTaskRunner()実行中は処理を中断します。そして、backgroundTaskRunner()の処理が終了した時点で処理を再開し、postExecutorRunner()を呼び出します。

 この関係を図にしたのが、図1です。

図1:非同期処理ブロック内でbackgroundTaskRunner()実行中は処理を中断
図1:非同期処理ブロック内でbackgroundTaskRunner()実行中は処理を中断

 Javaでは、このような処理を実現するために、LooperやHanderを利用して、処理を戻す方法をとりましたが、コルーチンを利用すると、以下のコードのように、もっとシンプルにコードが記述できます。

[リスト1]コルーチンによってシンプルにコードを記述できる
val result = backgroundTaskRunner(url)
postExecutorRunner(result)

コルーチンとコルーチンスコープ

 図1では、非同期で処理を行うbackgroundTaskRunner()とpostExecutorRunner()をひとつのブロック(非同期処理ブロック)として記述しています。Kotlinでは、この非同期処理のまとまりのことを、コルーチンといいます。そして、そのコルーチンが生存していられる環境をコルーチンスコープといいます。ということは、コルーチンを利用しようとするならば、まず、コルーチンスコープを用意し、そのコルーチンスコープからコルーチンを起動する手順を取ります。こちらに関しては、具体的なコードを交えて後述します。

AndroidでのKotlinコルーチンの利用

 さて、そのようなKotlinコルーチンをAndroidで利用する準備を進めていきましょう。

追加ライブラリが必要

 AndroidでKotlinコルーチンを利用するためには、標準のSDKのみでは無理であり、以下の2ライブラリを追加する必要があります(バージョン番号は、原稿執筆時点で最新のものを記述しています)。

org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9
androidx.lifecycle:lifecycle-runtime-ktx:2.2.0

 上のライブラリがKotlinコルーチン本体です。下のライブラリは、Androidでそれらコルーチンライブラリを効率よく利用するためのライブラリです。これらは、build.gradle(:app)のdependenciesに以下のコードを追加することで可能です。

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"

今回のサンプルもスケルトンプロジェクトを利用

 あらかじめAndroidプロジェクトを、Kotlin言語を利用する設定で作成し、上記ライブラリの追加を行うのが本筋です。しかし、その後のアプリ作成に必要なコードを全て掲載するには、今回も紙面がたりませんので、前回同様スケルトンプロジェクトを利用することにします。ダウンロードサンプル中のAsyncCoroutineSample-Skeleton.zipが該当します。解凍の上、Android Studioで読み込んで、一度エミュレータなどでアプリを実行してみてください。前回の図3の左側と同様の画面が表示されます。

スケルトンプロジェクトに記述済みのbackgroundTaskRunner()のポイント

 画面表示に関しては、前回のアプリと全く同じですが、MainActivity内のコードは、当然ですが全てKotlinに置き換わっています。その中でもいくつかポイントを見ていきましょう。

 まず、ワーカースレッドでHTTP通信を行うbackgroundTaskRunner()メソッドはリスト2のようなコードになっています。

[リスト2]MainActivity.kt
@WorkerThread  // (1)
private fun backgroundTaskRunner(url: String): String  {
	var result = ""
	val url = URL(url)
	val con = url.openConnection() as? HttpURLConnection
	con?.run {  // (2)
		requestMethod = "GET"
		connect()
		result = is2String(inputStream)  // (3)
		disconnect()
		inputStream.close()
	}
	return result  // (4)
}

 このbackgroundTaskRunner()は、先述のように、基本的に前回のBackgroundTaskクラスのrun()メソッド内の記述をKotlinコードに置き換えたものです。特筆すべき点は、(1)と(2)です、まず、図1にあるように、backgroundTaskRunner()はワーカースレッドで動作するので、(1)のように@WorkerThreadアノテーションを付与します。こちらは、Javaコードと同様です。(2)については、Kotlinでは、セーフコール演算子「?.」が利用できますので、HttpURLConnectionオブジェクトであるconがnullではない場合だけの処理がブロックとして記述できます。

 なお、Javaコード同様に、レスポンスとして取得したデータはInputStreamオブジェクトとなっていますので、それを文字列に変換する必要があります。そのためのis2String()メソッドも、Kotlinコードに置き換えたものをあらかじめスケルトンプロジェクトに記述しています。それを呼び出しているのが、(3)です。

 最終的に取得した天気情報JSON文字列を(4)でリターンします。

スケルトンプロジェクトに記述済みのpostExecutorRunner()のポイント

 リスト1にあるように、コルーチン内ではこのbackgroundTaskRunner()メソッドの戻り値を変数resultで受け取り、それを引数としてpostExecutorRunner()メソッドを呼び出しています。このpostExecutorRunner()メソッドそのものも、スケルトンプロジェクトにはあらかじめ記述しています。それは、リスト3のコードとなります。

[リスト3]MainActivity.kt
@UiThread
private fun postExecutorRunner(result: String) {
	val rootJSON = JSONObject(result)
	val cityName = rootJSON.getString("name")
	val weatherJSONArray = rootJSON.getJSONArray("weather")
	val weatherJSON = weatherJSONArray.getJSONObject(0)
	val description = weatherJSON.getString("description")
	val telop = cityName + "の天気"
	val desc = "現在は" + description + "です。"

	val tvWeatherTelop = findViewById<TextView>(R.id.tvWeatherTelop)
	val tvWeatherDesc = findViewById<TextView>(R.id.tvWeatherDesc)
	tvWeatherTelop.text = telop
	tvWeatherDesc.text = desc
}

 このコードも、基本的に前回のPostExecutorクラスのrun()メソッド内の記述を、そのままKotlinコードに置き換えた内容になっています。@UiThreadアノテーションが付与されていることも含めて、リスト2のbackgroundTaskRunner()よりもJavaコードに対応しているといえます。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

あなたにオススメ

著者プロフィール

  • WINGSプロジェクト 齊藤 新三(サイトウ シンゾウ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きた...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

バックナンバー

連載:2020年版Androidの非同期処理
All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5