WorkManager 高级进阶

原文链接:Advanced WorkManager topics | Android Developers

WorkManager 简化了精密任务请求的设定和安排,您可以在如下情境中使用其 API:

  • 任务链式请求(Chained sequence):按照指定顺序执行任务。
  • 唯一命名序列(Unique named sequence):在应用同时执行两个相同命名的序列时做决断。
  • 传递并返回值 的任务,包括链式任务中每个任务都将参数传给下一个的情况。

链式任务(Chained task)

您的应用可能想要按某个特定的顺序执行一系列任务。WorkManager 允许您创建一个任务序列并将其加入队列,该序列指定了多个任务及其执行的顺序。

例如,假设您的应用有三个 OneTimeWorkRequest: workAworkBworkC,且它们必须按这个顺序执行。欲将其加入队列,将第一个任务传入 WorkManager.beginWith(OneTimeWorkRequest)) 来创建一个序列,该方法返回一个定义了任务序列的 WorkContinuation 对象;接着,使用 WorkContinuation.then(OneTimeWorkRequest)) 来将剩余的 OneTimeWorkRequest 按顺序加入序列;最后,使用 WorkContinuation.enqueue() 来将整个序列加入队列。

  1. WorkManager.getInstance()
  2. .beginWith(workA)
  3. // 注意:WorkManager.beginWith() 返回一个
  4. // WorkContinuation 对象,因而下面调用的都是
  5. // WorkContinuation 的方法
  6. .then(workB) // 怕您忘了,then() 返回的是一个新的 WorkContinuation 实例
  7. .then(workC)
  8. .enqueue();

WorkManager 会根据每个任务的限制条件,按照请求的顺序执行它们。如果任意一个返回了 Result.failure()),整个序列就会结束。

您也可以向任意 beginWith(List<OneTimeWorkRequest>)) 或 then(List<OneTimeWorkRequest>)) 中传入多个 OneTimeWorkRequest 对象。那样的话,WorkManager 会在执行剩余序列前将它们全部(并行地)执行。例如:

  1. WorkManager.getInstance()
  2. // 首先,(并行地)执行所有 A 任务:
  3. .beginWith(Arrays.asList(workA1, workA2, workA3))
  4. // ……当所有 A 任务都执行结束后,执行一个 B 任务:
  5. .then(workB)
  6. // ……之后,再(按任意顺序)执行 C 任务:
  7. // (译者注:这是因为 C 任务之后没有其他任务了)
  8. .then(Arrays.asList(workC1, workC2))
  9. .enqueue();

您还可以通过 WorkContinuation.combine(List<OneTimeWorkRequest>)) 方法来并合多个任务链,从而创建更加复杂的序列。例如,假设您想要执行一个如下图所示的序列:

**图一** 您可以用 WorkContinuation 来设置复杂的链式任务

欲设置这样的序列,分别创建两个任务链,再将其并合到第三个中:

  1. WorkContinuation chain1 = WorkManager.getInstance()
  2. .beginWith(workA)
  3. .then(workB);
  4. WorkContinuation chain2 = WorkManager.getInstance()
  5. .beginWith(workC)
  6. .then(workD);
  7. WorkContinuation chain3 = WorkContinuation
  8. .combine(Arrays.asList(chain1, chain2))
  9. .then(workE);
  10. chain3.enqueue();

在这个例子中,WorkManagerworkB 前执行 workA,同时在 workD 前执行 workC。只有 workBworkD 都完成后,WorkManager 才执行 workE

注意:当 WorkManager 按顺序执行每个子链(subchain)时,它并不保证 chain1 中的任务是否会和 chain2 中的重叠。例如,workB 可能会在 workC 之前或之后执行,甚至可能同时执行。唯一可以确定的是,每个子链里的任务是按顺序执行的,也即:workBworkA 结束之前决不会开始。

WorkContinuation 的方法还有许多变体,用于简化处理特定的情况。欲了解更多细节,请查看 WorkContinuation 参考文档。

唯一任务序列(Unique work sequence)

您可以通过调用 beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)) 而非 beginWith(OneTimeWorkRequest)) 来创建一个唯一的任务序列。每个唯一任务序列都有一个命名,WorkManager 只允许一个任务序列同时使用相同的命名。当您创建一个新的唯一任务序列时,您可以指定 WorkManager 在已存在一个尚未完成的名字相同的任务序列时采取如下措施:

  • 取消已存在的序列,并用新的将其替换(replace)
  • 保持(keep) 已存在的序列,并忽略您的新请求。
  • 追加(append) 您的新序列到已存在的序列,并在已存在序列的最后一个任务结束后执行新序列的第一个任务。

当您的任务不应被多次加入队列时,唯一任务序列就能起到很大的作用。例如,如果您的应用需要将其数据与网络同步,您就能把一个命名为“同步”的任务序列加入队列,并指定 WorkManager 在已经存在一个相同名字的任务序列时将新的请求忽略。 唯一任务序列在您需要逐渐构建一个任务长链时也很有用。例如,一个图片编辑应用可能允许用户取消一个很长的链式操作,其中的每个取消操作都可能花费一段时间执行,但它们必须按照正确的顺序执行。在这种情况下,应用可以创建一个命名为“撤销”的任务链,并按需将每个撤销操作追加到任务链末尾。

输入参数并返回值

为了获得更好的灵活性,您可以将参数传入您的任务并让其返回结果。传入和返回的值都是键值对。 欲将一个参数传入至一个任务中,请在创建 WorkRequest 之前调用 WorkRequest.Builder.setInputData(Data)) 方法。该方法接收一个使用 Data.Builder 创建的 Data 对象。Worker 类可以通过调用 Worker.getInputData() 来获取这些参数。 要输出返回值,任务应将其包含在 Result 中(例如,返回 Result.success(Data))。您可以通过观测该任务的 WorkInfo 来获取输出。

例如,假设您有一个执行耗时计算的 Worker 类,那么这个 Worker 类的代码则会如下所示:

  1. // 定义 Worker 类:
  2. public class MathWorker extends Worker {
  3. // 定义输入参数的键:
  4. public static final String KEY_X_ARG = "X";
  5. public static final String KEY_Y_ARG = "Y";
  6. public static final String KEY_Z_ARG = "Z";
  7. // ……和输出结果的键:
  8. public static final String KEY_RESULT = "result";
  9. public MathWorker(
  10. @NonNull Context context,
  11. @NonNull WorkerParameters params) {
  12. super(context, params);
  13. }
  14. @Override
  15. public Result doWork() {
  16. // 获取输入参数(并指定缺省值):
  17. int x = getInputData().getInt(KEY_X_ARG, 0);
  18. int y = getInputData().getInt(KEY_Y_ARG, 0);
  19. int z = getInputData().getInt(KEY_Z_ARG, 0);
  20. // ……执行数学计算……
  21. int result = myCrazyMathFunction(x, y, z);
  22. //……设置好输出结果,我们就大功告成了!
  23. Data output = new Data.Builder()
  24. .putInt(KEY_RESULT, result)
  25. .build();
  26. return Result.success(output);
  27. }
  28. }

欲创建任务并传入参数,您可以使用如下的代码:

  1. // 创建 Data 对象:
  2. Data myData = new Data.Builder()
  3. // 我们需要传入三个整数:X、Y 和 Z
  4. .putInt(KEY_X_ARG, 42)
  5. .putInt(KEY_Y_ARG, 421)
  6. .putInt(KEY_Z_ARG, 8675309)
  7. // …… 并创建实际的 Data 对象:
  8. .build();
  9. // ……然后使用这些参数来创建一个 OneTimeWorkRequest 并将其加入队列
  10. OneTimeWorkRequest mathWork = new OneTimeWorkRequest.Builder(MathWorker.class)
  11. .setInputData(myData)
  12. .build();
  13. WorkManager.getInstance().enqueue(mathWork);

返回的值可以在任务的 WorkInfo 中获取:

  1. WorkManager.getInstance().getWorkInfoByIdLiveData(mathWork.getId())
  2. .observe(lifecycleOwner, info -> {
  3. if (info != null && info.getState().isFinished()) {
  4. int myResult = info.getOutputData().getInt(KEY_RESULT,
  5. myDefaultValue));
  6. // …… 用输出结果做点什么 ……
  7. }
  8. });

如果您把任务串成链,那么上一个任务的输出就可以用来作为下一个任务的输入。 如果该任务链是两个 OneTimeWorkRequest 的简单连接,那么第一个任务调用 Result.success(Data)) 来设置结果,而第二个任务则通过 getInputData() 来获取该结果。 如果该任务链结构复杂,例如多个任务都将结果输出到同一个任务中,那么您可以在 OneTimeWorkRequest.Builder 中定义一个 InputMerger 用于指定不同任务返回有相同键的结果时应当采取什么操作。