【Android Studio】Kotlin Coroutinesの使い方!非同期処理とスレッド

この記事からわかること

  • Android StudioKotlin Coroutines実装方法の使い方
  • 非同期処理を実装する方法
  • コルーチンとは?
  • runBlockinglaunchasyncメソッドの役割
  • suspend関数Dispatcherとは?
  • Unresolved reference: launch原因
  • Suspend function '関数名' should be called only from a coroutine or another suspend functionの原因

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

公式リファレンス:Kotlin coroutines on Android

環境

Kotlin Coroutinesとは?

Kotlin CoroutinesとはAndroidアプリ開発で非同期処理を実装できる公式ライブラリです。また「コルーチン」という言葉自体はAndroidで使用できる並行実行のデザインパターンのことを指しており、日本語に訳すと「特定の機能を担うルーチン」という的な意味になると思います。

Kotlin Coroutinesを使用することでこれまでコールバックなどで実行していたネットワーク処理やデータベースへの書き込み処理(I/O)、また処理に時間のかかるものなどを非同期的に(通常の流れとは異なる流れで)実行することでメインスレッドを活かし、アプリの対話性と生産性の向上を期待することができます。

処理のタイミングが異なる非同期処理を通常の同期処理の間に記述できることで見通しが良く、保守しやすいコードを実装することができます。

用語集

Kotlin Coroutinesを触るにあたって登場する言葉やクラスなどを自分なりにまとめてみました。

認識がずれていたら申し訳ないですが大体こんなイメージだと思います。

コルーチンとは?

Kotlin Coroutinesでは「コルーチン」という単位で非同期処理が管理されています。非同期処理を実装する場合バックグラウンドスレッドで実行するためコードの実行にラグが生じコールバックなどを使用する必要がありました。しかしKotlin Coroutinesを使用することで非同期処理をより簡潔に記述することが可能になります。また非同期のタスクを一時停止したり再開したりすることができるため、待ち時間のラグやコールバックネストの問題を解決できるようになるようです。

導入方法

Kotlin Coroutinesを使用できるようにするには以下のコードを「build.gradle(Module)」ファイルに記述し「Sync Now」を押します。私はAndroid Studio Flamingo(最新版)を使用していたところ依存関係を追記しなくても使用することができました。Android Studioのバージョンによっては依存関係の追加が必要ないかもしれません。

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}

Kotlin Coroutinesを使用した非同期処理

テスト実行環境で試しています。

Kotlin Coroutinesを使用した非同期処理のコードサンプルを見てみます。

import kotlinx.coroutines.*

fun main() = runBlocking { 
    launch { 
        delay(1000L) // 1秒遅らせる
        print("World!")
    }
    print("Hello") 
} 
// Hello
// World!

記述されている順番とは逆に出力されているのがわかります。このコードではrunBlockinglaunchがKotlin Coroutinesのコードになります。そしてasyncを含めた3つがよく使うコルーチンを作成するCoroutineBuilderです。

  1. runBlocking
  2. launch
  3. async

runBlocking

公式リファレンス:runBlocking

expect fun <T> runBlocking(
    context: CoroutineContext = EmptyCoroutineContext, 
    block: suspend CoroutineScope.() -> T
): T

runBlocking現在実行されているスレッドをブロックして処理を実行するコルーチンを作成するCoroutineBuilderです。スレッドをブロックして非同期処理を実行するため、アプリ内で使用するのであればメイン(UI)スレッドでの使用は避ける必要があります。実行してみると中で実行されるのは非同期処理のはずですがコードの流れ通りの処理順になっているのがわかります。

fun main()  {
    print("1") 
    runBlocking { 
      print("2") 
    }
    print("3") 
} // 123

launch

公式リファレンス:launch

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> Unit
): Job

launchメインスレッドをブロックせず(ノンブロッキング)にバックグラウンドで処理を行うコルーチンを作成するCoroutineBuilderです。コルーチンへの参照をJobインスタンスとして返します。そのJobインスタンスには値を含めることができないのでコルーチン内での計算結果などを返すことはできません。またlaunchはCoroutineScopeでのみ宣言されているためrunBlockingなどのコルーチンの中か明示的にスコープを指定しないと呼び出すことはできません

fun main() = runBlocking { 
    launch { 
        delay(1000L) // 1秒遅らせる
        print("World!")
    }
    print("Hello") 
}

明示的にスコープを指定した場合

fun main()  {
    print("1") 
    GlobalScope.launch { 
      print("2") 
    }
    print("3") 
} // 213

そのためスコープのない場所で呼び出そうとするとUnresolved reference: launchというエラーになります。

CoroutineScopeの種類

CoroutineScopeには使用する場所や状況に応じて種類が分けられています。GlobalScopeはアプリが終了するまで存在し続けるので使用はあまり推奨されていません。

  1. GlobalScope:アプリケーション全体のライフサイクルに関連付けられたスコープ。
  2. viewModelScope:ViewModel内で使えるスコープ。ViewModelが破棄されるとコルーチンも自動でキャンセル
  3. lifecycleScope:ActivityやFragment内で使えるスコープ。特定のライフサイクル状態に合わせたコルーチンを実行

async

公式リファレンス:async

fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> T
): Deferred<T>

asynclaunch同様にメインスレッドをブロックせず(ノンブロッキング)にバックグラウンドで処理を行うコルーチンを作成するCoroutineBuilderです。launchと異なるのは戻り値を指定できることです。戻ってくる型はJobの1種であるDeferred<T>型です。またasynclaunch同様にCoroutineScopeでのみ宣言されているためrunBlockingなどのコルーチンの中か明示的にスコープを指定しないと呼び出すことはできません

まずはサンプルコードをみてみます。

import kotlinx.coroutines.*

fun main() {
    runBlocking {
        // Deferredオブジェクトを取得
        val result1 = async { fetchDataFromRemote() }
        val result2 = async { fetchDataFromDatabase() }
        // 中身を取得
        val remoteData = result1.await()
        val databaseData = result2.await()

        print("$remoteData")
        print("$databaseData")
    }
}

suspend fun fetchDataFromRemote(): String {
    delay(2000)
    return "リモートサーバーからフェッチ"
}

suspend fun fetchDataFromDatabase(): String {
    delay(1500)
    return "データベースからフェッチ"
}

asyncメソッド内で実行された関数はString型を返す関数です。ですが戻り型はDeferred<String>なのでawaitメソッドを使用してStringのみを取り出します。awaitメソッドは非同期処理が完了するまで待機し結果を取得するメソッドです。

suspend関数

suspend関数はコルーチン内で実行する関数のことです。suspendキーワードを関数名の前に付与することでsuspend関数になります。またsuspend関数は通常の関数内から呼び出そうとするとSuspend function '関数名' should be called only from a coroutine or another suspend functionというエラーが発生するようになっています。

suspend fun fetchDataFromRemote(): String {
    delay(2000)
    return "リモートサーバーからフェッチ"
}

suspend fun fetchDataFromDatabase(): String {
    delay(1500)
    return "データベースからフェッチ"
}

CoroutineContext

CoroutineContextコルーチンの実行に必要な情報を保持するオブジェクトです。コルーチンがどのスレッドで実行されるか、エラーハンドリングの方法、タイムアウトの設定など、コルーチンの動作に影響を与えるさまざまな属性をキーと値のペアの集合として保持します。

Dispatcher

Dispatcherコルーチンを実行するスレッドを指定するオブジェクトです。この情報はCoroutineContextに保管されます。スレッドを指定するには引数にDispatchersを渡します。

GlobalScope.launch(Dispatchers.IO) { 
    print("2") 
}
Dispatchers.Main(メインスレッド)やDispatchers.IO(I/O処理に適したスレッド)などが定義されています。

スレッドの種類

withContext

withContextメインスレッドをブロックせず(ノンブロッキング)にバックグラウンドで処理を行うコルーチンを作成するCoroutineBuilderです。async/awaitと同じような振る舞いをしますが、async/awaitがcoroutineを新たに生成する一方、withContextは実行中のcoroutineのコンテキストを切り替えているだけです。なのでwithContextの方がパフォーマンスがよくメモリ消費も軽減されています。

fun main()  {
    print("1") 
    GlobalScope.launch { 
        withContext(Dispatchers.IO) {
            print("2") 
        }
      print("3") 
    }
    print("4") 
} // 2314

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index