WorkManager に関する高度なトピック

WorkManager を使用すると、複雑なタスク リクエストを簡単にセットアップしてスケジュール設定できます。 次のようなシナリオでこの API を使用できます。

タスクチェーン

アプリによっては、複数のタスクを特定の順序で実行することが必要となる場合があります。 WorkManager を使用すると、対象となる複数のタスクとその実行順序を指定する処理シーケンスを作成し、キューに登録できます。

たとえば、アプリに 3 つの OneTimeWorkRequest オブジェクト(workAworkBworkC)があり、各タスクをこの順序で実行する必要があるとします。各タスクをキューに登録するには、WorkManager.beginWith(OneTimeWorkRequest) メソッドを使用してシーケンスを作成し、最初の OneTimeWorkRequest オブジェクトを渡します。このメソッドが WorkContinuation オブジェクトを返すことで、タスクのシーケンスが定義されます。次に、WorkContinuation.then(OneTimeWorkRequest) を使用して残りの OneTimeWorkRequest オブジェクトを順番に追加します。最後に、WorkContinuation.enqueue() を使用してシーケンス全体をキューに登録します。

Kotlin

WorkManager.getInstance(myContext)
    .beginWith(workA)
        // Note: WorkManager.beginWith() returns a
        // WorkContinuation object; the following calls are
        // to WorkContinuation methods
    .then(workB)    // FYI, then() returns a new WorkContinuation instance
    .then(workC)
    .enqueue()

Java

WorkManager.getInstance(myContext)
    .beginWith(workA)
        // Note: WorkManager.beginWith() returns a
        // WorkContinuation object; the following calls are
        // to WorkContinuation methods
    .then(workB)    // FYI, then() returns a new WorkContinuation instance
    .then(workC)
    .enqueue();

WorkManager は、各タスクの指定制約に従って、リクエストされた順序でタスクを実行します。いずれかのタスクが Result.failure() を返すと、シーケンス全体が終了します。

また、複数の OneTimeWorkRequest オブジェクトを beginWith(List<OneTimeWorkRequest>) 呼び出しや then(List<OneTimeWorkRequest>) 呼び出しに渡すこともできます。複数の OneTimeWorkRequest オブジェクトを単一のメソッド呼び出しに渡すと、WorkManager は、シーケンスの残りを実行する前に、渡されたタスクをすべて並列実行します。次に例を示します。

Kotlin

WorkManager.getInstance(myContext)
    // First, run all the A tasks (in parallel):
    .beginWith(Arrays.asList(workA1, workA2, workA3))
    // ...when all A tasks are finished, run the single B task:
    .then(workB)
    // ...then run the C tasks (in parallel):
    .then(Arrays.asList(workC1, workC2))
    .enqueue()

Java

WorkManager.getInstance(myContext)
    // First, run all the A tasks (in parallel):
    .beginWith(Arrays.asList(workA1, workA2, workA3))
    // ...when all A tasks are finished, run the single B task:
    .then(workB)
    // ...then run the C tasks (in parallel):
    .then(Arrays.asList(workC1, workC2))
    .enqueue();

WorkContinuation.combine(List<OneTimeWorkRequest>) メソッドを使用すると、複数のチェーンを結合して、さらに複雑なシーケンスを作成できます。たとえば、次のようなシーケンスを実行するとします。

図 1. WorkContinuation を使用して、複雑なタスクチェーンをセットアップできます。

このシーケンスをセットアップするには、2 つの独立したチェーンを作成して、それを 3 つ目のチェーンに結合します。

Kotlin

val chain1 = WorkManager.getInstance(myContext)
    .beginWith(workA)
    .then(workB)
val chain2 = WorkManager.getInstance(myContext)
    .beginWith(workC)
    .then(workD)
val chain3 = WorkContinuation
    .combine(Arrays.asList(chain1, chain2))
    .then(workE)
chain3.enqueue()

Java

WorkContinuation chain1 = WorkManager.getInstance(myContext)
    .beginWith(workA)
    .then(workB);
WorkContinuation chain2 = WorkManager.getInstance(myContext)
    .beginWith(workC)
    .then(workD);
WorkContinuation chain3 = WorkContinuation
    .combine(Arrays.asList(chain1, chain2))
    .then(workE);
chain3.enqueue();

この場合、WorkManagerworkA を実行してから workB を実行します。また、workC を実行してから workD を実行します。workBworkD が両方とも終了した後、WorkManagerworkE を実行します。

特定の状況に応じて簡略型を提供する WorkContinuation メソッドには、さまざまなバリエーションがあります。詳しくは、WorkContinuation のリファレンスをご覧ください。

一意処理シーケンス

beginWith(OneTimeWorkRequest) 呼び出しではなく beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) 呼び出しを使用してシーケンスを開始することにより、一意処理シーケンスを作成できます。一意処理シーケンスはそれぞれ固有の名前を持ちます。WorkManager は、特定の名前の処理シーケンスを一度に 1 つだけ許可します。新しい一意処理シーケンスを作成する際は、すでに同じ名前の未完了シーケンスがある場合の WorkManager の動作を指定します。

  • REPLACE - 既存のシーケンスをキャンセルして、新しいシーケンスに置き換えます。
  • KEEP - 既存のシーケンスを維持して、新しいリクエストを無視します。
  • APPEND - 既存のシーケンスに新しいシーケンスを追加して、既存のシーケンスの最後のタスクが完了した後に新しいシーケンスの最初のタスクを実行します。

一意処理シーケンスは、複数回キューに登録すべきでないタスクがある場合に役立ちます。たとえば、データをネットワークに同期する必要があるアプリで「sync」という名前のシーケンスをキューに登録するとします。このとき、すでに同じ名前のシーケンスが存在する場合は、新しいタスクを無視するように指定できます。一意処理シーケンスは、長いタスクチェーンを段階的に構築する必要がある場合にも役立ちます。たとえば、写真編集アプリで、一連の編集を元に戻すことができる機能を提供しているとします。元に戻す操作はそれぞれ時間がかかる可能性がありますが、正しい順序で実行する必要があります。この場合、アプリは「元に戻す」用のチェーンを作成し、元に戻す各操作を必要に応じてチェーンに追加できます。

入力パラメータと戻り値

柔軟性を高めるため、引数を渡したタスクが結果を返すように指定できます。渡す値と戻り値はそれぞれ Key-Value ペアです。タスクに引数を渡すには、WorkRequest オブジェクトを作成する前に WorkRequest.Builder.setInputData(Data) メソッドを呼び出します。このメソッドは、Data オブジェクト(Data.Builder を使用して作成)を受け取ります。Worker クラスは、Worker.getInputData() を呼び出すことで、これらの引数にアクセスできます。戻り値を出力するには、タスクの Result 内に組み込む必要があります(たとえば、Result.success(Data))。タスクの WorkInfo をモニタリングすることで、出力を取得できます。

たとえば、時間のかかる計算を行う Worker クラスがあるとします。次に Worker クラスの例を示します。

Kotlin

// Define the parameter keys:
const val KEY_X_ARG = "X"
const val KEY_Y_ARG = "Y"
const val KEY_Z_ARG = "Z"

// ...and the result key:
const val KEY_RESULT = "result"

// Define the Worker class:
class MathWorker(context : Context, params : WorkerParameters)
    : Worker(context, params)  {

    override fun doWork(): Result {
        val x = inputData.getInt(KEY_X_ARG, 0)
        val y = inputData.getInt(KEY_Y_ARG, 0)
        val z = inputData.getInt(KEY_Z_ARG, 0)

        // ...do the math...
        val result = myLongCalculation(x, y, z);

        //...set the output, and we're done!
        val output: Data = workDataOf(KEY_RESULT to result)

        return Result.success(output)
    }
}

Java

// Define the Worker class:
public class MathWorker extends Worker {

    // Define the parameter keys:
    public static final String KEY_X_ARG = "X";
    public static final String KEY_Y_ARG = "Y";
    public static final String KEY_Z_ARG = "Z";
    // ...and the result key:
    public static final String KEY_RESULT = "result";

    public MathWorker(
        @NonNull Context context,
        @NonNull WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Result doWork() {
        // Fetch the arguments (and specify default values):
        int x = getInputData().getInt(KEY_X_ARG, 0);
        int y = getInputData().getInt(KEY_Y_ARG, 0);
        int z = getInputData().getInt(KEY_Z_ARG, 0);

        // ...do the math...
        int result = myLongCalculation(x, y, z);

        //...set the output, and we're done!
        Data output = new Data.Builder()
            .putInt(KEY_RESULT, result)
            .build();
        return Result.success(output);
    }
}

処理を作成して引数を渡すには、次のようなコードを使用します。

Kotlin

val myData: Data = workDataOf("KEY_X_ARG" to 42,
                       "KEY_Y_ARG" to 421,
                       "KEY_Z_ARG" to 8675309)

// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
        .setInputData(myData)
        .build()
WorkManager.getInstance(myContext).enqueue(mathWork)

Java

// Create the Data object:
Data myData = new Data.Builder()
    // We need to pass three integers: X, Y, and Z
    .putInt(KEY_X_ARG, 42)
    .putInt(KEY_Y_ARG, 421)
    .putInt(KEY_Z_ARG, 8675309)
    // ... and build the actual Data object:
    .build();

// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
OneTimeWorkRequest mathWork = new OneTimeWorkRequest.Builder(MathWorker.class)
        .setInputData(myData)
        .build();
WorkManager.getInstance(myContext).enqueue(mathWork);

戻り値は、タスクの WorkInfo から取得できます。

Kotlin

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(mathWork.id)
        .observe(this, Observer { info ->
            if (info != null && info.state.isFinished) {
                val myResult = info.outputData.getInt(KEY_RESULT,
                      myDefaultValue)
                // ... do something with the result ...
            }
        })

Java

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(mathWork.getId())
    .observe(lifecycleOwner, info -> {
         if (info != null && info.getState().isFinished()) {
           int myResult = info.getOutputData().getInt(KEY_RESULT,
                  myDefaultValue));
           // ... do something with the result ...
         }
    });

タスクをチェーン化した場合、1 つのタスクの出力を、チェーン内の次のタスクの入力として使用できます。単一の OneTimeWorkRequest の後に別の単一の OneTimeWorkRequest が続くシンプルなチェーンの場合、Result.success(Data) を呼び出すことで、最初のタスクが結果を返し、getInputData() を呼び出すことで、次のタスクが最初のタスクの結果を取得します。チェーンが複雑な場合(たとえば、複数のタスクが単一の後続タスクにすべての出力を送信する場合)、OneTimeWorkRequest.BuilderInputMerger を定義することで、複数のタスクが同一のキーを持つ出力を返した場合の動作を指定できます。

参考情報

WorkManager について詳しくは、以下の参考情報をご覧ください。

サンプル