【Kotlin/Android】WorkManagerでバックグラウンド処理!OneTime/Periodicの違い

この記事からわかること

  • Android Studio/KotlinWorkManager使い方
  • アプリ停止中にバックグラウンド処理実行する方法
  • OneTime/Periodic違い
  • アプリが停止しても処理継続するには?

index

[open]

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

みんなの誕生日

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

posted withアプリーチ

環境

Androidのバックグラウンド処理

Androidアプリでバックグラウンド処理を実装する方法はいくつかあります。ただ単にメインスレッドではなくバックグラウンドスレッドで処理を実行したい場合アプリが停止している状態でもバックグラウンドで処理を継続したい場合など目的は異なるかと思います。

実装できる方法は以下になります。今回はWorkManagerについてまとめていきます。

実装できる方法

WorkManager

WorkManagerAndroid Jetpackライブラリの一部のバックグラウンドタスクの実行を可能にしているライブラリです。ネットワークの接続状況やデバイスの充電状態などの条件を識別してバックグラウンドタスクを管理することができます。また一回だけのタスク処理だけでなく、定期的な間隔ごとにタスクを実行することも可能です。

WorkManagerはOneTimeWorkRequestPeriodicWorkRequestがあります。両者はタスクを実行したい間隔によって異なります。

WorkManagerの種類

WorkManagerでできること

実装方法

  1. Workerクラスの定義(実行したい処理を定義)
  2. WorkRequestを作成(OneTime or Periodic)
  3. WorkManagerにWorkRequestをエンキュー(ワーカー開始)

1.Workerクラスの定義(実行したい処理を定義)

実行したい処理はWorkerを継承して独自のクラスを定義して用意します。doWorkメソッドの中に処理を記述します。この中は常にバックグラウンドスレッドで実行されます。例えば無限にカウントアップし続ける処理を実装したい場合は以下のようになります。

class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    // Volatileは異なるスレッドからの操作が即座に反映されるようにする
    @Volatile
    private var isStopped = false

    override fun doWork(): Result {
        Log.d("Worker", Thread.currentThread().getName()) // androidx.work-1

        val taskThread = Thread(Runnable {
            var count = 0
            while (!isStopped) {
                count++
                Log.d("Worker", "Count: $count")
                try {
                    Thread.sleep(1000) // 1秒間隔でカウントアップ
                } catch (e: InterruptedException) {
                    Thread.currentThread().interrupt()
                }
            }
            Log.d("Worker", "Task stopped")
        })
        taskThread.start()

        // タスクが停止するまで待機
        taskThread.join()

        Log.d("Worker", "Task END")
        return Result.success() // タスクが成功したどうかを返す
    }

    override fun onStopped() {
        super.onStopped()
        isStopped = true
    }
}

2.WorkRequestを作成(OneTime or Periodic)

続いてOneTimeWorkRequestまたはPeriodicWorkRequestのどちらかに対してbuildメソッドを使用してWorkRequestを作成します。

val workRequest = OneTimeWorkRequest.Builder(MyWorker::class.java)
  .build()

3.WorkManagerにWorkRequestをエンキュー(ワーカー開始)

最後にWorkManagerインスタンスを取得してenqueueメソッドの引数にWorkRequestを渡すとワーカー(doWork)が実行されるようになります。

WorkManager.getInstance(this).enqueue(workRequest)

PeriodicWorkRequest

PeriodicWorkRequestを使用して一定間隔ごとに処理を実行したい場合は引数に実行間隔と単位を指定します。実行間隔として指定できるのは最小15分までで、PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLISとして15分が定義されています。

また内部的にフレックス時間(定期的な作業の実行時間を調整するための時間範囲のこと)も定義されておりこれは最小5分となっているため大体15〜20分ごとに処理を実行することができるようになっているようです。

val periodicWorkRequest = PeriodicWorkRequest.Builder(
      MyWorker::class.java,
      PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, // 実行間隔
      TimeUnit.MINUTES // 単位
  ).build()

条件を指定する

ワーカーの実行を行う際に条件を設けることも可能です。例えば繰り返し処理にAPIリクエストを実装していた場合ネットワーク接続が必要にしたので以下のように.setRequiredNetworkType(NetworkType.CONNECTED)を指定することで必須条件にすることができます。

val constraints = Constraints.Builder()
  // ネットワーク接続を必須にする
  .setRequiredNetworkType(NetworkType.CONNECTED)
  .build()

// WorkRequestを作成し、制約を設定
val workRequest = OneTimeWorkRequest.Builder(MyWorker::class.java)
    .setConstraints(constraints)
    .build()

処理を停止させる

ワーカーを停止させたい場合はWorkRequestidcancelWorkByIdメソッドに渡すことで停止させることができます。停止させるとonStoppedメソッドが呼ばれます。

// 必要なタイミングでWorkerを停止する
WorkManager.getInstance(context).cancelWorkById(workRequest.id)

全てのワーカーを停止させたい場合はcancelAllWorkメソッドを使用します。

val workManager = WorkManager.getInstance(context)
workManager.cancelAllWork()

データを渡す

WorkManagerに対して何かしらのデータを渡したい場合はData.Builder()を使用します。putStringなどを使用しキーと値を渡し、WorkRequestsetInputDataメソッドで渡します。

val inputData = Data.Builder()
    .putString("input_key", "Hello, WorkManager!")
    .putInt("input_number", 123)
    .build()
  
val workRequest: OneTimeWorkRequest = OneTimeWorkRequestBuilder()
    .setInputData(inputData)
    .build()

これでワーカーのdoWorkメソッド内からinputData.getStringなどで取得することができます。

val inputString = inputData.getString("input_key") ?: throw IllegalArgumentException("文字列は必須です")
val inputNumber = inputData.getInt("input_number", 0)

アプリをキルしても処理が継続する

WorkManagerはアプリ自体がバックグラウンド状態(ホーム画面や他のアプリ起動、アプリのキル、電源OFF(スリープ)状態)でも動作してくれます。放置し続けて30分ほど様子をみましたが、正常に動作していることを確認できました。

注意点としてdoWorkメソッドの返り値であるResult.success()を早々に返してしまうとワーカーは完了したとみなし、正常にループ処理を続けてくれなくなるので注意してください。

override fun doWork(): Result {
    Log.d("Worker", Thread.currentThread().getName()) // androidx.work-1

    val taskThread = Thread(Runnable {
        var count = 0
        while (!isStopped) {
            count++
            Log.d("Worker", "Count: $count")
            try {
                Thread.sleep(1000) // 1秒間隔でカウントアップ
            } catch (e: InterruptedException) {
                Thread.currentThread().interrupt()
            }
        }
        Log.d("Worker", "Task stopped")
    })
    taskThread.start()

    // タスクが停止するまで待機(これが大事)
    taskThread.join()

    Log.d("Worker", "Task END")
    return Result.success() // タスクが成功したどうかを返す
}

Kotlin Coroutinesを使用する場合

WorkerでKotlin Coroutinesを使用したい場合はCoroutineWorkerを使用することで簡単に実装できるようになります。WorkerではなくCoroutineWorkerを継承させてサブクラスを定義することでdoWorkメソッドにsuspendが付与されるようになります。

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

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

searchbox

スポンサー

ProFile

ame

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

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

New Article

index