Android Kotlin基礎講座 09.2:WorkManager

タスク:定期的なWorkRequestを定義する

Workerはタスクの単位を定義し、WorkRequestはタスクがいつ、どのように実行されるべきかを定義します。WorkRequestクラスの具体的な実装は二つあります。

  • OneTimeWorkRequestクラスは一回限りのタスク用です。(一度しか起こらない)
  • PeriodicWorkRequestクラスは定期的に行われるタスク用です。(間隔を置いて繰り返される)

タスクは一度だけ行われたり、定期的に繰り返されたりするので、必要に応じてクラスを選択してください。

Note: 定期的に行われるタスクの最小インターバルは15分間です。定期タスクに制約として初期遅延を持たせることはできません。

このタスクでは、WorkRequestを定義、スケジューリングし、前のタスクで作成したworkerを実行させます。

ステップ1:繰り返すタスクをセットアップ

Androidアプリにおいて、Applicationクラスはアクティビティやサービスなどのような全てのコンポーネントを含むベースクラスです。アプリケーション用の処理やパッケージが作成された際、Applicationクラス(またはそのサブクラス)が他のクラスよりも先にインスタンス化されます。

このサンプルアプリでは、DevByteApplicationクラスがApplicationクラスのサブクラスです。
DevByteApplicationクラスはWorkManagerをスケジューリングするのに適した場所といえます。

  1. DevByteApplicationクラス内で、setupRecurringWork()というメソッドを作成し、バックグラウンドで繰り返すタスクのセットアップを行います。
/**
* Setup WorkManager background job to 'fetch' new network data daily.
*/
private fun setupRecurringWork() {
}
  1. setupRecurringWork()メソッド内で、一日一回定期的に行わせるタスクのリクエストを作成・初期化します。PeriodicWorkRequestBuilder()メソッドを使います。
    前回のタスクで作成したRefreshDataWorkerクラスを渡してください。また繰り返し間隔として1と、TimeUnit.DAYSも渡してください。
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .build()

エラーを解消するために、java.util.concurrent.TimeUnitをインポートしてください。

ステップ2:WorkManagerでWorkRequestをスケジューリングする

WorkRequestを定義したあとは、WorkManagerのenqueueUniquePeriodicWork()メソッドを使って、WorkRequestをスケジューリングできます。このメソッドを使うことで、独自に名付けられたPeriodicWorkRequestをキューに追加することができます。キューでは一度に一つのみ、特定の名前のPeriodicWorkRequestをアクティブにすることができます。

例えば、ある一つだけ同期処理をアクティブにしたいとします。ある同期処理が処理中の場合、それを動作させるか、または別の新しいタスクに置き換えるかをExistingPeriodicWorkPolicyを使って選択することができます。

WorkRequestのスケジューリング方法に関して、より詳しく知りたい方はWorkMangerのドキュメンテーションをご覧ください。

  1. RefreshDataWorkerクラス内、クラスのはじめにcompanionオブジェクトを追加してください。タスク名を定義して、このworkerに一意性を持たせます。
companion object {
   const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
  1. DevByteApplicationクラス内、setupRecurringWork()メソッドの最後で、enqueueUniquePeriodicWork()メソッドを使って先ほどのworkをスケジューリングします。
    ExistingPeriodicWorkPolicy用にKEEP enumを渡してください。
    PeriodicWorkRequestパラメーターとして、repeatingRequestを渡してください。
WorkManager.getInstance().enqueueUniquePeriodicWork(
       RefreshDataWorker.WORK_NAME,
       ExistingPeriodicWorkPolicy.KEEP,
       repeatingRequest)

もし処理中(未完了)で同じ名前のタスクが存在した場合、ExistingPeriodicWorkPolicy.KEEPパラメーターはWorkManagerに前のタスクをキープさせ、新規タスクのリクエストを破棄させるようにしてくれます。

ベストプラクティス:onCreate()メソッドはメインスレッドで動作しています。onCreate()内でロングランニング操作を行うと、UIスレッドをブロックし、アプリの読み込みに遅延を発生させる場合があります。この問題を避けるために、Timberの初期化やWorkMangerのスケジューリングなどといったタスクはメインスレッドではなく、コルーチンの中で行いましょう。

  1. DevByteApplicationクラスのはじめで、CoroutineScopeオブジェクトを作成してください。コンストラクタのパラメーターとして、Dispatchers.Defaultを渡してください。
private val applicationScope = CoroutineScope(Dispatchers.Default)
  1. DevByteApplicationクラス内にコルーチンを起動するためのdelayedInit()というメソッドを追加してください。
private fun delayedInit() {
   applicationScope.launch {
   }
}
  1. delayedInit()メソッド内でsetupRecurringWork()を呼び出してください。
  2. Timberの初期化をonCreate()メソッドから、このメソッドの中に移してください。
private fun delayedInit() {
   applicationScope.launch {
       Timber.plant(Timber.DebugTree())
       setupRecurringWork()
   }
}
  1. DevByteApplicationクラス内、onCreate()メソッドの最後に、delayedInit()メソッドの呼び出しを追加してください。
override fun onCreate() {
   super.onCreate()
   delayedInit()
}
  1. Android Studioウィンドウの下のほうにあるLogcatパネルを開いてください。検索ボックスにRefreshDataWorkerと入力してフィルタ―をかけてください。
  2. アプリを起動してください。WorkManagerが即座に繰り返しタスクをスケジューリングします。

    LogcatにはWorkリクエストがスケジュールされたことと、正常に動作したことを示す文が表示されていることを確認してください。
D/RefreshDataWorker: Work request for sync is run
I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

WM-WorkerWrapperログはWorkManagerライブラリから表示されているので、このログメッセージを変更することはできません。

Step 3: (任意) 最小の間隔でWorkRequestをスケジューリングする

このステップでは、タスクの間隔を1日から15分に減らします。これをすることで、実行中のWorkリクエストのログを実際に確認することができます。

  1. DevByteApplicationクラス内、setupRecurringWork()メソッド内のrepeatingRequstの定義をコメントアウトしてください。15分の繰り返し間隔を使ったworkリクエストを追加してください。
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
//        .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
       .build()
  1. Android StudioでLogcatを開き、RefreshDataWorkerでフィルターをかけてください。前回のログをクリアするためにはClear logcatアイコンをクリックしてください。
  2. アプリを起動してください。WorkManagerが即座に繰り返すタスクをスケジューリングします。LogcatパネルでWorkリクエストが15分ごとに実行されていることを確認してください。新しいログが表示されるまでには15分待つ必要があります。アプリは放置しても閉じても大乗bうです。Work Managerは動作し続けます。

    間隔が15分より短かったり、長かったりする場合があることを確認してください。
    (正確なタイミングはOSのバッテリー最適化に関係する問題になります)
12:44:40 D/RefreshDataWorker: Work request for sync is run
12:44:40 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
12:59:24 D/RefreshDataWorker: Work request for sync is run
12:59:24 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:15:03 D/RefreshDataWorker: Work request for sync is run
13:15:03 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:29:22 D/RefreshDataWorker: Work request for sync is run
13:29:22 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:44:26 D/RefreshDataWorker: Work request for sync is run
13:44:26 I/WM-WorkerWrapper: Worker result SUCCESS for Work

おめでとうございます!workerを作成し、WorkManagerを使ってリクエストをスケジュールすることができました。しかし問題が一つ残っています。それは如何なる制約も指定していないということです。
WorkManagerはタスクを一日一回行うようにスケジューリングします。端末のバッテリー残量が少なかったり、スリープ状態であったり、ネットワーク接続がない状態であってもです。これは端末のバッテリーとパフォーマンスに影響を与え、不快なユーザーエクスペリエンスを生み出してしまうことになるでしょう。

次のタスクでは、制約を追加することで、この問題を対処していきます。