Android Kotlin基礎講座 09.2:WorkManager

タスク:制約を追加する

前のタスクでは、WorkManagerを使ってWorkリクエストをスケジューリングしました。このタスクではタスクをいつ執行するかの基準を追加していきます。

WorkRequestを定義する際に、Workerがいつ動作すべきかを決める制約を指定することができます。例として、あるタスクは端末がWi-Fiに接続されている状態の時だけに実行されるように指定したりできます。また、backoffポリシーを指定してタスクを再試行させることもできます。
サポートされている制約はConstraints.Builder内のセットメソッドになります。これについてより詳しく学びたい方は、Defining your Work Requests(英文)をご覧ください。

PeriodicWorkRequestと制約

PeriodicWorkRequestなどの繰り返すタスク用のWorkRequestは、それがキャンセルされるまで複数回実行します。最初の実行は即座に行われるか、与えられた制約に適合した際に行われます。

次の実行は次の実行までのインターバルの間に起こります。WorkManagerはOSのバッテリーに関与しているので、実行が遅れる場合もあります。例として端末がDozeモードのときなどがあげられます。

ステップ1:Constraints(制約)オブジェクトを追加し、制約を設定する

このステップでは、Constraintsオブジェクトを作成し、それに一つの制約を設定します。今回設定するのはネットワークタイプの制約になります。(制約が一つだけのほうがログを確認するのが容易になるためです。後のステップでは他の制約も追加します)

  1. DevByteApplicationクラス内、setupRecurringWork()のはじめで、Constraints型のvalを定義してください。Constraints.Builder()メソッドを使用します。
val constraints = Constraints.Builder()

エラーを解消するために、androidx.work.Constraintsをインポートしてください。

  1. setRequiredNetworkType()メソッドを使って、ネットワークタイプの制約をconstraintsオブジェクトに追加してください。UNMETERED(従量制) enumを使ってWorkリクエストが端末が従量制ネットワークを利用しているときのみ実行されるようにします。
.setRequiredNetworkType(NetworkType.UNMETERED)
  1. build()メソッドを使ってbuilderからconstraintsを生成してください。
val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .build()

そうしたら新しく作成されたConstraintsオブジェクトをWorkリクエストに設定する必要があります。

  1. DevByteApplicationクラス内、setupRecurringWork()メソッド内で、Constraintsオブジェクトを繰り返すWorkリクエストであるrepeatingRequestに設定してあげましょう。制約を設定するためには、build()の呼び出しの上にsetConstraints()を追加してください。
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
               .setConstraints(constraints)
               .build()

ステップ2:アプリを起動してログを確認する

このステップでは、アプリを起動して制約が課されたWorkリクエストがバックグラウンドで間隔を置いて動作していることを確認します。

  1. 実機、またはエミュレーターからアプリをアンインストールして、以前までにスケジュールされたタスクをキャンセルします。
  2. LogcatをAndroid Studioで開いてください。Logcat内のClear logcatアイコンをクリックして以前までのログを削除してください。workでフィルターをかけてください。
  3. 制約がどのように機能しているかを確認するために、端末のWi-Fiをオフにしてください。現在のコードでは従量制ネットワーク上でのみリクエストが動作することを示す制約ひとつだけが設定されています。Wi-Fiがオフになっているため、端末は従量制、非従量制どちらのネットワークにも接続されていません。従ってこの制約は満たされていません。
  4. アプリを起動してLogcatを確認してください。WorkManagerがバックグラウンドタスクを即座にスケジューリングします。ネットワーク制約が満たされていないため、タスクは実行されません。
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
  1. Wi-FiをオンにしてLogcatを確認してください。ネットワーク制約が満たされている間は、スケジュールされたバックグラウンドタスクがだいたい15分ごとに実行されます。
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
11:31:47 D/RefreshDataWorker: Work request for sync is run
11:31:47 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]
11:46:45 D/RefreshDataWorker: Work request for sync is run
11:46:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:03:05 D/RefreshDataWorker: Work request for sync is run
12:03:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:16:45 D/RefreshDataWorker: Work request for sync is run
12:16:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:31:45 D/RefreshDataWorker: Work request for sync is run
12:31:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:47:05 D/RefreshDataWorker: Work request for sync is run
12:47:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
13:01:45 D/RefreshDataWorker: Work request for sync is run
13:01:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

ステップ3:さらに制約を追加する

このステップでは、以下の制約をPeriodicWorkRequestに追加します。

  1. バッテリーが少ない状態でない。
  2. 端末が充電中である。
  3. デバイスがアイドル(スリープ)状態:API23以上でのみ使用可能です。

DevByteApplicationクラス内で、以下の実装を行ってください。

  1. DevByteApplicationクラス内、setupRecurringWork()メソッド内でバッテリーが少なくないときのみworkリクエストが実行されるように指定します。build()の呼び出しの前に制約を追加し、setRequiresBatteryNotLow()メソッドを使ってください。
.setRequiresBatteryNotLow(true)
  1. 端末が充電中のときのみリクエストが実行されるように更新します。build()の呼び出しの前に制約を追加し、setRequiresChargin()メソッドを使ってください。
.setRequiresCharging(true)
  1. デバイスがアイドル状態のときのみリクエストが実行されるように更新します。build()の呼び出しの前に制約を追加し、setRequiresDeviceIdle()メソッドを使ってください。この制約はユーザーが端末を積極的に使っていないときにのみリクエストを実行するようにします。これはAndroid 6.0以上でのみ使えるので、SDKバージョンをM以上にします。
.apply {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       setRequiresDeviceIdle(true)
   }
}

以下がconstraintsオブジェクトの最終的な定義になります。

val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresBatteryNotLow(true)
       .setRequiresCharging(true)
       .apply {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
               setRequiresDeviceIdle(true)
           }
       }
       .build()
  1. setupRecurringWork()メソッド内のリクエスト間隔を一日一回に戻してください。
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .setConstraints(constraints)
       .build()

以下がsetupRecurringWork()メソッドの最終的な実装になります。リクエストがスケジュールされたときにトラックできるようにログをつけています。

private fun setupRecurringWork() {

       val constraints = Constraints.Builder()
               .setRequiredNetworkType(NetworkType.UNMETERED)
               .setRequiresBatteryNotLow(true)
               .setRequiresCharging(true)
               .apply {
                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                       setRequiresDeviceIdle(true)
                   }
               }
               .build()
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
               .setConstraints(constraints)
               .build()
       
       Timber.d("Periodic Work request for sync is scheduled")
       WorkManager.getInstance().enqueueUniquePeriodicWork(
               RefreshDataWorker.WORK_NAME,
               ExistingPeriodicWorkPolicy.KEEP,
               repeatingRequest)
   }
  1. 前回までにスケジューリングされたworkリクエストを削除するために、アプリをアンインストールしてください。
  2. アプリを起動してください。WorkManagerが即座にリクエストをスケジューリングします。Workリクエストは全ての制約を満たしたときに一日一回実行されます。
  3. このWorkリクエストはアプリがインストールされている限りは、アプリが動作していなくてもバックグラウンドで実行されます。したがって確認作業が終わったらアプリを端末から削除しておくべきでしょう。

お疲れさまでした。バッテリーフレンドリーなリクエストを実装することができました。

完成済みプロジェクト

お疲れさまでした。完成済みプロジェクトは以下からダウンロードできます。

DevBytesWorkManager