【Android Studio/Kotlin Coroutines】Jobの使い方!遅延実行やキャンセル

【Android Studio/Kotlin Coroutines】Jobの使い方!遅延実行やキャンセル

この記事からわかること

  • Android StudioKotlin Coroutines実装方法の使い方
  • 非同期処理を実装する方法
  • Jobとは?
  • コルーチン管理方法

index

[open]

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

みんなの誕生日

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

posted withアプリーチ

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

環境

Kotlin Coroutinesとは?

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

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

Jobとは?

公式リファレンス:Job

interface Job : CoroutineContext.Element

Kotlin CoroutinesではJobという単位でコルーチンを一意に識別し、そのライフサイクルが管理されています。Jobを操作することで処理の開始、キャンセル、完了を制御することが可能になります。

コルーチンをJobインスタンスとして管理するためにはグローバル関数Job()を使用するかCoroutineBuilderであるlaunchasyncを使用し、その返り値であるJobインスタンス(asyncのDeferredオブジェクトもJobの一種)を保持するだけです。処理の実行自体は非同期で即座に開始しますが時間のかかる処理の場合、一定時間経過でcancelメソッドを使用することでコルーチンの実行をキャンセルすることができます。

import kotlinx.coroutines.*
fun main() {
    val job = GlobalScope.launch {
        repeat(10) { i ->
            println("Job is running: $i")
            delay(1000)
        }
    }

    // 3秒後にジョブをキャンセルする
    runBlocking {
      delay(3000)
    }
    job.cancel()

    println("Main function finished")
}

出力結果

Job is running: 0
Job is running: 1
Job is running: 2
Job is running: 3
Main function finished

JobインスタンスはそのJobの状態(ステータス)を保持しておりisActiveなどのプロパティから状態を取得することができます。

import kotlinx.coroutines.*
fun main() {
    runBlocking {
        // Jobインスタンスを取得
        val childJob1 = launch { createRemoteData() }
        // Deferredインスタンスを取得
        val childJob2 = async { fetchDataFromRemote() }
        
        print(childJob1.isActive)    // true
        
        // Jobをキャンセルする
        childJob1.cancel()
        
        print(childJob1.isActive)    // false
        print(childJob1.isCompleted) // false
        print(childJob1.isCancelled) // true
        
        //  Deferredインスタンスから中身を取得(await:非同期が終了するまで待機)
        val remoteData = childJob2.await()

        print(childJob2.isActive)    // false
        print(childJob2.isCompleted) // true
        print(childJob2.isCancelled) // false

        print("$remoteData")
    }
}

suspend fun createRemoteData() {
    delay(1000)
    print("リモートサーバーにデータを作成")
}

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

Jobのステータス

Jobのステータスは以下のように6種類に分かれています。それぞれの状態でのプロパティの値も以下のように変化します。launchasyncで生成したJobインスタンスは最初からActive状態になっているようです。

State isActive isCompleted isCancelled
New (optional initial state) false false false
Active (default initial state) true false false
Completing (transient state) true false false
Cancelling (transient state) false false true
Cancelled (final state) false true true
Completed (final state) false true false

Jobの親子関係

Jobは親子関係を持った階層構造で管理することができ、親Jobの中に複数の子Jobを保持することが可能です。親のJobが明示的にキャンセルされた場合はその親が保持している子Jobも全てキャンセルされます。

fun main() {
    val parentJob = Job()

    val childJob = GlobalScope.launch(parentJob) {
        repeat(10) { i ->
            print("Job is running: $i")
            delay(1000)
        }
    }

    // 親Jobをキャンセルする (子Jobは出力されない)
    parentJob.cancel()

    print("Main function finished")
}

Jobが完了した後に処理を実行する:invokeOnCompletion

公式リファレンス:invokeOnCompletion

abstract fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle

Jobが完了状態などに移行した際に処理を実行したい場合はinvokeOnCompletionメソッドを使用します。CompletionHandlerからはThrowable?型で例外を受け取ることが可能です。

parentJob.invokeOnCompletion { throwable ->
    if (throwable != null) {
        // ジョブが例外とともに完了した場合の処理
    } else {
        // ジョブが正常に完了した場合の処理
    }
}

Jobを遅延実行する方法

生成したJobを即座に開始するのではなく遅延して実行させたい場合はlaunchasyncの引数startCoroutineStart.LAZYを指定します。何も指定しない場合は即座にコルーチンが実行され「Coroutine is running」が出力されます。

fun main() {
    val job = GlobalScope.launch {
        println("Coroutine is running")
    }
}

CoroutineStart.LAZYを指定するとjob.start()がされるまで実行を遅延させることができます。とjob.start()を呼び出さないとコルーチン内の処理は実行されません。

fun main() {
    val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
        println("Coroutine is running")
    }
    job.start()   
}

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

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

searchbox

スポンサー

ProFile

ame

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

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

New Article

index