【Kotlin/Android】WorkManagerでバックグラウンド処理!OneTime/Periodicの違い
この記事からわかること
- Android Studio/KotlinのWorkManagerの使い方
- アプリ停止中にバックグラウンドで処理を実行する方法
- OneTime/Periodicの違い
- アプリが停止しても処理を継続するには?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Android Studio:Flamingo
- Kotlin:1.8.20
Androidのバックグラウンド処理
Androidアプリでバックグラウンド処理を実装する方法はいくつかあります。ただ単にメインスレッドではなくバックグラウンドスレッドで処理を実行したい場合やアプリが停止している状態でもバックグラウンドで処理を継続したい場合など目的は異なるかと思います。
実装できる方法は以下になります。今回はWorkManagerについてまとめていきます。
実装できる方法
- Kotlin Coroutines
- WorkManager
- Service
WorkManager
WorkManagerはAndroid Jetpackライブラリの一部のバックグラウンドタスクの実行を可能にしているライブラリです。ネットワークの接続状況やデバイスの充電状態などの条件を識別してバックグラウンドタスクを管理することができます。また一回だけのタスク処理だけでなく、定期的な間隔ごとにタスクを実行することも可能です。
WorkManagerはOneTimeWorkRequest
とPeriodicWorkRequest
があります。両者はタスクを実行したい間隔によって異なります。
WorkManagerの種類
- OneTimeWorkRequest・・・1度のみの処理のスケジュールを設定
- PeriodicWorkRequest・・・一定間隔ごとに繰り返すスケジュールを設定
WorkManagerでできること
- バックグラウンドスレッドでの処理実行
- バックグラウンド(アプリ起動中)での処理継続実行
- バックグラウンド(アプリ停止やキル状態)での処理継続実行
- 定期間隔での処理の起動(最短15分)
- ネットワーク状態などの条件指定して処理を起動
実装方法
- Workerクラスの定義(実行したい処理を定義)
- WorkRequestを作成(OneTime or Periodic)
- 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()
処理を停止させる
ワーカーを停止させたい場合はWorkRequest
のid
をcancelWorkById
メソッドに渡すことで停止させることができます。停止させるとonStopped
メソッドが呼ばれます。
// 必要なタイミングでWorkerを停止する
WorkManager.getInstance(context).cancelWorkById(workRequest.id)
全てのワーカーを停止させたい場合はcancelAllWork
メソッドを使用します。
val workManager = WorkManager.getInstance(context)
workManager.cancelAllWork()
データを渡す
WorkManagerに対して何かしらのデータを渡したい場合はData.Builder()
を使用します。putString
などを使用しキーと値を渡し、WorkRequest
のsetInputData
メソッドで渡します。
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
が付与されるようになります。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。