🔜

【Android】バックグラウンドタスクをいい感じに実行したい ▶︎WorkManagerを使う

2023/03/31に公開

こちらのまとめ記事になります。
https://zenn.dev/tbsten/scraps/a19209c41933a7

参考

WorkManager とそのメリット

WorkManager を使うと(ある種の)バックグラウンドで行うべきタスクをいい感じに実行することができます。

メリット

単に処理を実行するだけでなく、

  • 「Wi-Fi に繋いでる時だけ」「保存容量が ○○GB 以上の時」「充電 ○%以上」のような条件を指定して、その条件が揃うまで待ってから実行する
  • 処理 1 -> 処理 2,3,4 -> 処理 5,6 のように並列実行スケジューリングができる。
    • 前段階の処理の実行結果を引き継ぐことができる

いつ使うか

使用例 1

https://developer.android.com/codelabs/basic-android-kotlin-compose-workmanager?hl=ja&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-7-pathway-1%3Fhl%3Dja%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-workmanager#0

こちらのコードラボでは最終的に

初期化して
↓↓
画像を加工して(ぼかして)
↓↓
加工した画像を保存

といったように WorkManager を使っていました。

使用例 2

面倒なので 実装していませんが、

画像フィルター1 , 画像フィルター2 , 画像フィルター3 (並行に実行)
↓↓
画像をローカルに保存 , 画像をサーバに送信 (並行に実行)

みたいなこともできちゃいそうです。

いつ使わないか

Google 曰く、バックグラウンドタスク(UI スレッド外で行った方がいいタスク)は 3 つのカテゴリに分類されるそう。

タスクの分類 説明 おすすめの技術(ソリューション)
即時 ユーザがアプリ操作中にタスクが完了する必要があるタスク Kotlin コルーチン
延期 ユーザがアプリを離れても実行され続ける必要がなく、ぴったし同じ時間に実行しなくてもいいタスク (いい感じのタイミングで勝手にやっといてね系) WorkManager 👈👈👈 本題
定刻 ユーザがアプリを離れても実行され続ける必要がなく、ぴったし同じ時間に実行しないといけないタスク (指定した時間に実行してね系) AlarmManager

「バックグラウンドタスクの種類によって使い分けてね」だそうです。

参考:
https://developer.android.com/guide/background/?hl=ja

実装する

1. Worker(CoroutineWorker)

1 つのタスクを表します。Worker を継承して doWork メソッドをオーバーライドして使います。Worker 実行時に doWork がよばれる感じです。
イメージとしては Worker を後述のクラスたちを使って組み合わせて 1 つの処理を実現する感じになります。

お察しの通り doWork で suspend 関数を使いたい時は CoroutineWorker を利用します。

  • inputData というフィールドに Worker に与えられた入力情報が入っています。
  • 戻り値として Result.success()Result.faliure() を返します。
    • Result.success() は引数として出力する値を Data で(workDataOf()で生成する)指定することができます。

class SendImageToServerWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {

  override suspend fun doWork(): Result {
    // inputDataで入力情報にアクセスできる
    val input1 = inputData.getString("key-1")
    // TODO: ここで重たい処理をする
    // 成功時は Result.success()
    return Result.success()
    // workDataOf で実行結果を返せる -> 後続のWorkerが利用できる!
    return Result.success(
      workDataOf(
        "key-2" to 123,
        "key-3" to true,
      )
    )
    // 失敗時は Result.faliure()
    return Result.faliure()
  }

}

2. WorkRequest と WorkManager で実行

  1. 先ほど作成した Worker を WorkRequest でラップする(理由は後述)
  • WorkRequest は OneTimeWorkRequestBuilder().build() などを使ってインスタンス化する
  1. WorkManager で WorkRequest 達をつなげて実行する
WorkManagerで実行する
val workManager = WorkManager.getInstance(context)

val requestHoge = OneTimeWorkRequestBuilder<HogeWorker>().build()
val requestFuga = OneTimeWorkRequestBuilder<FugaWorker>().build()

workManager
  .beginWith(requestHoge)
  .then(requestFuga)
  .enqueue()

WorkRequest で実行条件を指定

OneTimeWorkRequestBuilder はbuild()の前までにメソッドチェーンで実行条件などを設定できます。

以下はネットワークにつながってからSendImageWorker を実行する例です。

OneTimeWorkRequestBuilder<SendImageWorker>()
  .setConstraints(
    Constraints.Builder()
      .setRequiredNetworkType(NetworkType.CONNECTED). // ネットワークにつながっている時
      .build()
  )
  .build()

WorkRequest で入力データを指定

以下のように inputData をセットしておくことで Worker 内でinputDataとして取得できます。

OneTimeWorkRequestBuilder<SendImageWorker>()
  .setInputData(
    workDataOf("input1" to 123),
    workDataOf("input2" to "hoge"),
  )
  .build()
Worker内
override suspend fun doWork(): Result {
  val input1 = inputData.getInt("input1", 0)
  val input2 = inputData.getInt("input2")
  ...
}

PeriodicWorkRequestBuilder で定期的に実行する

これまでのように OneTimeWorkRequestBuilder で作成した WorkRequest は 1 回限り実行されます。

その代わりにPeriodicWorkRequestBuilderを使用すれば一定間隔で定期的に WorkRequest を実行できます。

val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)  // 1時間に1回
           .build()

複数の WorkRequest を並列に実行する

beginWiththenでは引数に WorkRequest の List を渡すこともできます。List で渡すとそれらは並列に実行されます。

workManager
  .beginWith(listOf(request1, request2))
  .then(listOf(request3, request4))

Discussion