WorkRequest の定義

スタートガイドでは、シンプルな WorkRequest を作成してキューに登録する方法を説明しました。

このガイドでは、WorkRequest オブジェクトを定義してカスタマイズし、以下のような一般的なユースケースに対処する方法について説明します。

  • 1 回限りの処理と繰り返し処理のスケジュールを設定する
  • 処理の制約を設定する(Wi-Fi や充電の必要など)
  • 処理の実行の遅延が最小限になるよう保証する
  • 再試行とバックオフの戦略を設定する
  • 入力データを処理に渡す
  • タグを使用して関連する処理をグループ化する

概要

WorkManager では、処理は WorkRequest を介して定義されます。WorkManager で処理のスケジュールを設定するには、まず WorkRequest オブジェクトを作成してキューに登録する必要があります。

Kotlin

val myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest)

Java

WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);

WorkRequest オブジェクトには、WorkManager による処理のスケジュール設定と実行に必要なすべての情報が含まれています。これには、処理を実行するために満たす必要がある制約、遅延や繰り返し間隔などのスケジュール設定の情報、再試行の設定のほか、処理が依存している入力データも含まれます。

WorkRequest 自体は抽象基底クラスです。このクラスには、リクエストの作成に使用できる派生実装として、OneTimeWorkRequestPeriodicWorkRequest の 2 つがあります。名前が示すように、OneTimeWorkRequest は繰り返しのない処理のスケジュール設定に便利です。一方、PeriodicWorkRequest は、一定の間隔で繰り返す処理のスケジュール設定に適しています。

1 回限りの処理のスケジュール設定

追加の設定が不要な単純な処理の場合は、静的メソッドの from を次のように使用します。

Kotlin

val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

Java

WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

より複雑な処理には、ビルダーを使用できます。

Kotlin

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()

Java

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // Additional configuration
       .build();

優先処理のスケジュール設定

WorkManager 2.7.0 で、優先処理というコンセプトが導入されました。これにより、WorkManager が重要な処理を実行している間も、システムはリソースへのアクセスをより適切に制御できます。

優先処理には次の特徴があります。

  • 重要性: 優先処理は、ユーザーにとって重要なタスクや、ユーザーが開始するタスクに適しています。
  • スピード: 優先処理は、直ちに開始して数分以内に完了する短いタスクに最適です。
  • 割り当て: フォアグラウンド実行の時間を制限するシステムレベルの割り当てにより、優先ジョブを開始できるかどうかが決まります。
  • 電源管理: バッテリー セーバーや Doze などの電源管理の制限は、優先処理にあまり影響しません。
  • レイテンシ: システムの現在の負荷が許せば、優先処理はすぐに実行されます。つまり、レイテンシの影響を受けやすく、後で実行するようにスケジュールすることはできません。

優先処理のユースケースとしては、チャットアプリでメッセージや添付画像を送信する処理が考えられます。同様に、支払いやサブスクリプションのフローでも優先処理を必要とする場合があります。これは、こういったタスクはユーザーにとって重要であり、バックグラウンドですばやく実行され、直ちに開始する必要があるためです。また、ユーザーがアプリを閉じても実行を継続する必要があります。

割り当て

システムは、優先ジョブの実行が可能となる前に、実行時間を優先ジョブに割り当てる必要があります。実行時間は無制限ではありません。代わりに、各アプリは実行時間の割り当てを受け取ります。アプリが実行時間を使用して割り当てに達すると、割り当てが更新されるまで優先処理を実行できなくなります。これにより、Android がアプリ間でリソースのバランスをより効果的に調整できるようになります。

アプリで使用できる実行時間の長さは、スタンバイ バケットとプロセスの重要性に基づきます。

デベロッパーは、実行割り当てが優先ジョブの即時実行を許さないときの優先ジョブの動作を決めることができます。詳しくは、以下のスニペットをご覧ください。

優先処理の実行

WorkManager 2.7 以降では、アプリは setExpedited() を呼び出して、WorkRequest が優先ジョブを使用して可能な限り早く実行されるように宣言できます。次のコード スニペットは、setExpedited() の使用方法の例を示しています。

Kotlin

val request = OneTimeWorkRequestBuilder<SyncWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context)
    .enqueue(request)

Java

OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<T>()
    .setInputData(inputData)
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build();

この例では、OneTimeWorkRequest のインスタンスを初期化し、その setExpedited() を呼び出します。すると、このリクエストは優先処理になります。割り当てが許せば、バックグラウンドで直ちに実行が開始されます。割り当てが使用されている場合、OutOfQuotaPolicy パラメータは、リクエストを通常の非エクスプレス処理として実行する必要があることを示します。

下位互換性とフォアグラウンド サービス

優先ジョブに対して下位互換性を維持するために、WorkManager は Android 12 より前のプラットフォーム バージョンでフォアグラウンド サービスを実行することがあります。フォアグラウンド サービスはユーザーに通知を表示できます。

ワーカーの getForegroundInfoAsync() メソッドと getForegroundInfo() メソッドを使用すると、Android 12 より前の setExpedited() を呼び出したときに WorkManager が通知を表示することが可能になります。

タスクが優先ジョブとして実行されるようリクエストするには、すべての ListenableWorkergetForegroundInfo メソッドを実装する必要があります。

Android 12 以降をターゲットとする場合は、対応する setForeground メソッドで引き続きフォアグラウンド サービスを使用できます。

ワーカー

ワーカーは、行っている処理が優先されているのかどうかを認識しません。ただし Android の一部のバージョンでは、WorkRequest が優先されている場合、ワーカーは通知を表示できます。

そのために、WorkManager には getForegroundInfoAsync() メソッドが用意されており、必要に応じて WorkManager が ForegroundService を開始する通知を表示できるように実装する必要があります。

CoroutineWorker

CoroutineWorker を使用する場合は、getForegroundInfo() を実装する必要があります。その後、doWork() 内の setForeground() に渡します。そうすることで、Android 12 より前のバージョンで通知が作成されます。

次の例を考えてみましょう。

  class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters):
   CoroutineWorker(appContext, workerParams) {

   override suspend fun getForegroundInfo(): ForegroundInfo {
       return ForegroundInfo(
           NOTIFICATION_ID, createNotification()
       )
   }

   override suspend fun doWork(): Result {
       TODO()
   }

    private fun createNotification() : Notification {
       TODO()
    }

}

割り当てのポリシー

デベロッパーは、アプリが実行割り当てに達したときの優先処理の動作を制御できます。続行するには、setExpedited() に次のいずれかを渡します。

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST: ジョブが通常の処理リクエストとして実行されます。上記のスニペットでは、これを行っています。
  • OutOfQuotaPolicy.DROP_WORK_REQUEST: 割り当てが足りない場合にリクエストがキャンセルされます。

優先処理の遅延

システムは、指定された優先ジョブが呼び出されると、できる限り速やかに実行しようと試みます。ただし、他のタイプのジョブと同様に、システムによって新しい優先ジョブの開始が遅延されることがあります。たとえば、次のような場合です。

  • 負荷: システム負荷が高すぎる。これは実行中のジョブが多すぎる場合や、システムに十分なメモリがないときに発生する可能性があります。
  • 割り当て: 優先ジョブの割り当て上限を超えている。優先処理では、アプリ スタンバイ バケットに基づく割り当てシステムにより、ローリング時間枠内の最大実行時間が制限されます。優先処理に使用される割り当ては、他のタイプのバックグラウンド ジョブに使用されるものよりも厳格な制限になっています。

定期的な処理のスケジュール設定

アプリでは、特定の処理を定期的に実行しなければならない場合があります。たとえば、データのバックアップ、アプリでの最新のコンテンツのダウンロード、ログのサーバーへのアップロードといったタスクです。

以下に、PeriodicWorkRequest を使用して、定期的に実行される WorkRequest オブジェクトを作成する方法を示します。

Kotlin

val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()

Java

PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
           // Constraints
           .build();

この例では、1 時間間隔で処理のスケジュールが設定されています。

間隔は、繰り返しの間の最小時間として定義されます。ワーカーの正確な実行タイミングは、WorkRequest オブジェクトで使用する制約とシステムで実行される最適化に左右されます。

柔軟な実行間隔

処理の性質上、実行タイミングが重要になる場合は、図 1 に示すように、PeriodicWorkRequest が各間隔のフレックス期間内に実行されるように設定できます。

定期的なジョブのフレックス間隔を設定できます。繰り返し間隔と、繰り返し間隔の終わりに一定の時間を指定するフレックス間隔を定義します。WorkManager は、各サイクルのフレックス間隔内のある時点で、ジョブの実行を試みます。

図 1. 図は、処理を実行できるフレックス期間のある繰り返し間隔を示しています。

フレックス期間のある定期的な処理を定義するには、PeriodicWorkRequest を作成するときに repeatInterval と一緒に flexInterval を渡します。フレックス期間は repeatInterval - flexInterval から始まり、間隔の最後まで続きます。

以下に、毎時間の最後の 15 分間に実行される定期的な処理の例を示します。

Kotlin

val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval (the period cycle)
       15, TimeUnit.MINUTES) // flexInterval
    .build()

Java

WorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class,
               1, TimeUnit.HOURS,
               15, TimeUnit.MINUTES)
           .build();

繰り返し間隔は PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS 以上で、フレックス間隔は PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS 以上である必要があります。

定期的な処理に対する制約の影響

定期的な処理に制約を適用できます。たとえば、ユーザーのデバイスが充電中の場合にのみ処理が実行されるように、処理リクエストに制約を追加できます。この場合、定義された繰り返し間隔が経過しても、この条件が満たされるまで PeriodicWorkRequest は実行されません。そのため、実行間隔内に条件が満たされない場合、特定の処理の実行が遅延したり、スキップされたりする可能性があります。

処理の制約

制約により、最適な条件が満たされるまで処理が延期されます。WorkManager では以下の制約を使用できます。

NetworkType 処理の実行に必要なネットワークの種類を制約します。(例: Wi-Fi(UNMETERED))。
BatteryNotLow true に設定すると、デバイスが電池残量低下モードになっている場合には処理が実行されません。
RequiresCharging true に設定すると、デバイスが充電中の場合にのみ処理が実行されます。
DeviceIdle true に設定すると、ユーザーのデバイスがアイドル状態になった後にのみ処理が実行されます。これは、ユーザーのデバイスで実行中の他のアプリのパフォーマンス低下を招くような、一括操作を実行する場合に便利です。
StorageNotLow true に設定すると、ユーザーのデバイスの保存容量が少なすぎる場合、処理は実行されません。

一連の制約を作成して処理に関連付けるには、Contraints.Builder() を使用して Constraints インスタンスを作成し、それを WorkRequest.Builder() に割り当てます。

たとえば、次のコードは、ユーザーのデバイスが充電中で、かつ Wi-Fi に接続している場合にのみ実行される WorkRequest を作成します。

Kotlin

val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()

val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()

Java

Constraints constraints = new Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresCharging(true)
       .build();

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setConstraints(constraints)
               .build();

複数の制約が指定されている場合、すべての制約が満たされた場合にのみ処理が実行されます。

処理の実行中に制約が満たされなくなると、WorkManager がワーカーを停止します。その後、すべての制約が満たされると、処理が再試行されます。

処理の遅延

処理に制約がない場合、または処理がキューに登録されたときにすべての制約が満たされている場合、システムはすぐに処理を実行できます。処理をすぐに実行しない場合、最小限の初期遅延の後に処理を開始するよう指定できます。

次の例は、処理がキューに登録されてから 10 分以上経過してからタスクを実行するように設定する方法を示しています。

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

Java

WorkRequest myWorkRequest =
      new OneTimeWorkRequest.Builder(MyWork.class)
               .setInitialDelay(10, TimeUnit.MINUTES)
               .build();

この例では、OneTimeWorkRequest の初期遅延を設定する方法を示していますが、PeriodicWorkRequest の初期遅延を設定することもできます。その場合、定期的な処理が最初に実行されるときにのみ遅延が発生します。

再試行とバックオフに関するポリシー

WorkManager で処理を再試行する必要がある場合、ワーカーから Result.retry() を返すことができます。その後、バックオフ遅延バックオフ ポリシーに従って処理のスケジュールが変更されます。

  • バックオフ遅延では、最初の試行後に処理を再試行するまでに待機する最小時間を指定します。この値は 10 秒(または MIN_BACKOFF_MILLIS)以上で指定できます。

  • バックオフ ポリシーでは、以降の再試行のバックオフ遅延が経時的にどの程度増加するかを定義します。WorkManager は、LINEAREXPONENTIAL の 2 つのバックオフ ポリシーをサポートしています。

すべての WorkRequest にバックオフ ポリシーとバックオフ遅延があります。デフォルトのポリシーは 30 秒の遅延が発生する EXPONENTIAL ですが、このポリシーは WorkRequest の設定でオーバーライドできます。

以下に、バックオフ遅延とバックオフ ポリシーのカスタマイズ例を示します。

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setBackoffCriteria(
                       BackoffPolicy.LINEAR,
                       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                       TimeUnit.MILLISECONDS)
               .build();

この例では、最小バックオフ遅延が、最小値の 10 秒に設定されています。ポリシーが LINEAR であるため、再試行間隔は新しい試行ごとに約 10 秒ずつ増加します。たとえば、最初の実行が Result.retry() で終わった場合、10 秒後に再試行されます。以降の試行後に処理が Result.retry() を返し続けた場合、再試行の間隔は 20 秒、30 秒、40 秒と順に増加していきます。バックオフ ポリシーが EXPONENTIAL に設定されている場合、再試行間隔は 20 秒、40 秒、80 秒のように増加していきます。

処理へのタグ付け

すべての処理リクエストに一意の識別子があります。この識別子を使用して後で処理を識別し、処理のキャンセル進行状況のモニタリングを行うことができます。

論理的に関連する処理のグループがある場合は、処理項目にタグ付けすると便利な場合があります。タグ付けをすることで、処理リクエストのグループを一緒に処理できます。

たとえば WorkManager.cancelAllWorkByTag(String) は、特定のタグを持つすべての処理リクエストをキャンセルします。また WorkManager.getWorkInfosByTag(String) は、現在の処理の状態を判断するために使用できる WorkInfo オブジェクトのリストを返します。

次のコードは、「cleanup」タグを処理に追加する方法を示しています。

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
       .addTag("cleanup")
       .build();

最後に、1 つの WorkRequest に複数のタグを追加できます。内部的には、これらのタグは一連の文字列として保存されます。WorkRequest に関連付けられているタグのセットを取得するには、WorkInfo.getTags() を使用します。

Worker クラスから、ListenableWorker.getTags() を介してタグのセットを取得できます。

入力データの割り当て

処理を実行するために、入力データが必要になる場合があります。たとえば、画像のアップロードを行う処理では、入力としてアップロードする画像の URI が必要になります。

入力値は Key-Value ペアとして Data オブジェクトに保存され、処理リクエストで設定できます。WorkManager は、処理の実行時に入力 Data を処理に渡します。Worker クラスは、Worker.getInputData() を呼び出すことで入力引数にアクセスできます。次のコードは、入力データを必要とする Worker インスタンスを作成する方法と、処理リクエストでインスタンスを送信する方法を示しています。

Kotlin

// Define the Worker requiring input
class UploadWork(appContext: Context, workerParams: WorkerParameters)
   : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       val imageUriInput =
           inputData.getString("IMAGE_URI") ?: return Result.failure()

       uploadFile(imageUriInput)
       return Result.success()
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
   .setInputData(workDataOf(
       "IMAGE_URI" to "http://..."
   ))
   .build()

Java

// Define the Worker requiring input
public class UploadWork extends Worker {

   public UploadWork(Context appContext, WorkerParameters workerParams) {
       super(appContext, workerParams);
   }

   @NonNull
   @Override
   public Result doWork() {
       String imageUriInput = getInputData().getString("IMAGE_URI");
       if(imageUriInput == null) {
           return Result.failure();
       }

       uploadFile(imageUriInput);
       return Result.success();
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
WorkRequest myUploadWork =
      new OneTimeWorkRequest.Builder(UploadWork.class)
           .setInputData(
               new Data.Builder()
                   .putString("IMAGE_URI", "http://...")
                   .build()
           )
           .build();

同様に、Data クラスを使用して戻り値を出力できます。入力データと出力データについて詳しくは、入力パラメータと戻り値のセクションをご覧ください。

次のステップ

状態とモニタリング ページで、処理の状態と、処理の進行状況のモニタリング方法について詳しく学習します。