【Android】バックグラウンドタスクをいい感じに実行したい ▶︎WorkManagerを使う
こちらのまとめ記事になります。
参考
WorkManager とそのメリット
WorkManager
を使うと(ある種の)バックグラウンドで行うべきタスクをいい感じに実行することができます。
メリット
単に処理を実行するだけでなく、
- 「Wi-Fi に繋いでる時だけ」「保存容量が ○○GB 以上の時」「充電 ○%以上」のような条件を指定して、その条件が揃うまで待ってから実行する
- 処理 1 -> 処理 2,3,4 -> 処理 5,6 のように並列実行スケジューリングができる。
- 前段階の処理の実行結果を引き継ぐことができる
いつ使うか
使用例 1
こちらのコードラボでは最終的に
初期化して
↓↓
画像を加工して(ぼかして)
↓↓
加工した画像を保存
といったように WorkManager を使っていました。
使用例 2
面倒なので 実装していませんが、
画像フィルター1
,画像フィルター2
,画像フィルター3
(並行に実行)
↓↓
画像をローカルに保存
,画像をサーバに送信
(並行に実行)
みたいなこともできちゃいそうです。
いつ使わないか
Google 曰く、バックグラウンドタスク(UI スレッド外で行った方がいいタスク)は 3 つのカテゴリに分類されるそう。
タスクの分類 | 説明 | おすすめの技術(ソリューション) |
---|---|---|
即時 | ユーザがアプリ操作中にタスクが完了する必要があるタスク | Kotlin コルーチン |
延期 | ユーザがアプリを離れても実行され続ける必要がなく、ぴったし同じ時間に実行しなくてもいいタスク (いい感じのタイミングで勝手にやっといてね系) |
WorkManager 👈👈👈 本題 |
定刻 | ユーザがアプリを離れても実行され続ける必要がなく、ぴったし同じ時間に実行しないといけないタスク (指定した時間に実行してね系) | AlarmManager |
「バックグラウンドタスクの種類によって使い分けてね」だそうです。
参考:
実装する
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 で実行
- 先ほど作成した Worker を
WorkRequest
でラップする(理由は後述)
- WorkRequest は
OneTimeWorkRequestBuilder().build()
などを使ってインスタンス化する
-
WorkManager
で WorkRequest 達をつなげて実行する
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()
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 を並列に実行する
beginWith
やthen
では引数に WorkRequest の List を渡すこともできます。List で渡すとそれらは並列に実行されます。
workManager
.beginWith(listOf(request1, request2))
.then(listOf(request3, request4))
Discussion