WorkManager 高级主题

WorkManager 简化了复杂任务请求的设置和调度。您可以在以下情景中使用这些 API:

链接任务

您的应用可能需要按特定顺序运行多个任务。您可以通过 WorkManager 创建工作序列并对序列中的任务进行排队。该工作序列将指定多个任务以及这些任务的运行顺序。

例如,假设您的应用有三个 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 设置复杂的链接任务。

如需设置该序列,请创建两个单独的链,然后将它们联接成第三个链:

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();

在这种情况下,WorkManager 会在 workB 之前运行 workA。它还会在 workD 之前运行 workC。在 workBworkD 都完成后,WorkManager 会运行 workE

WorkContinuation 方法有多种变体,可在特定情况下提供简写形式。如需了解详情,请参阅 WorkContinuation 参考文档。

唯一工作序列

您可以创建唯一工作序列,方法是通过调用 beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)(而非 beginWith(OneTimeWorkRequest))开始创建序列。每个唯一工作序列都有一个名称,WorkManager 不允许同时存在多个具有相同名称的工作序列。当您创建新的唯一工作序列时,如果已存在同名的未完成序列,您需要指定 WorkManager 应执行什么操作:

  • 取消现有序列,并用新序列替换
  • 保留现有序列,并忽略新请求
  • 将新序列附加到现有序列后面,在现有序列的最后一个任务完成后,开始运行新系列的第一个任务

当您有不能够多次排队的任务时,唯一工作序列将非常有用。例如,如果您的应用需要将其数据同步到网络,您可能需要对一个名为“sync”的序列进行排队,并指定当已经存在具有该名称的序列时,应该忽略新的任务。当您需要逐步构建一个长任务链时,也可以利用唯一工作序列。例如,照片编辑应用可能允许用户撤消一长串操作。其中的每一项撤消操作可能都需要一些时间来完成,但必须按正确的顺序执行。在这种情况下,应用可以创建一个“撤消”链,并根据需要将每个撤消操作附加到该链上。

输入参数和返回值

为了提高灵活性,您可以将参数传递给任务并让任务返回结果。传递和返回的值是键值对。如需向任务传递参数,请先调用 WorkRequest.Builder.setInputData(Data) 方法,然后再创建 WorkRequest 对象。该方法将接受您使用 Data.Builder 创建的 Data 对象。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 ...
         }
    });

如果您链接多个任务,一个任务的输出可以作为任务链中下一个任务的输入。如果是简单链(即一个 OneTimeWorkRequest 后面跟着另一个 OneTimeWorkRequest),第一个任务通过调用 Result.success(Data) 返回其结果,下一个任务通过调用 getInputData() 提取该结果。如果是更复杂的链(例如有多个任务都将输出发送给同一个后续任务),您可以在 OneTimeWorkRequest.Builder 上定义 InputMerger,以指定当多个不同任务返回具有相同键的输出时应执行什么操作。

其他资源

如需详细了解 WorkManager,请参阅下面列出的其他资源。

示例