注册

WorkManager :工作链

工作链

工作链也是WorkManager的一个非常重要的功能。

你可以使用WorkManager创建工作链并将其加入队列。工作链用于指定多个依存任务并定义这些任务的运行顺序。当需要以特定顺序运行多个任务时,此功能尤其有用。

例如,假设您的应用有三个 OneTimeWorkRequest对象:workAworkB 和 workC。这些任务必须按该顺序运行。如需对这些任务进行排队,请使用 WorkManager.beginWith(OneTimeWorkRequest)方法创建一个序列,并传递第一个 OneTimeWorkRequest对象;该方法会返回一个 WorkContinuation对象,以定义一个任务序列。然后,使用 WorkContinuation.then(OneTimeWorkRequest)依次添加剩余的 OneTimeWorkRequest 对象;最后,使用 WorkContinuation.enqueue()对整个序列进行排队:

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

A-B-C.png WorkManager会根据每个任务的指定约束,按请求的顺序运行任务。如果有任务返回Result.failure(),整个序列结束。

您还可以将多个 OneTimeWorkRequest对象传递给任何 beginWith(List)和 then(List)调用。如果您向单个方法调用传递多个 OneTimeWorkRequest对象,WorkManager会并行运行所有这些任务,然后再运行序列中的其他任务。例如:

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

A1A2A3-B-C1C2.png

您可以使用 WorkContinuation.combine(List)方法联接多个任务链来创建更为复杂的序列。例如,假设您要运行像这样的序列:

workmanager-chain.svg

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

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。在 workB 和 workD 都完成后,WorkManager会运行 workE

注意:虽然 WorkManager会按顺序运行各个子链,但并不保证 chain1 中的任务如何与 chain2 中的任务重叠。例如,workB 可能会在 workC 的前面或后面运行,或者两者也可能会同时运行。唯一可以保证的就是每个子链中的任务将按顺序运行,即 workB 会在 workA 完成之后再启动。

上面我们介绍了工作链的基本知识和基本的方法。下面我们来看一个示例。在本例中,有 3 个不同的工作器作业配置为运行(可能并行运行)。然后这些工作器的结果将联接起来,并传递给正在缓存的工作器作业。最后,该作业的输出将传递到上传工作器,由上传工作器将结果上传到远程服务器。


WorkManager.getInstance(myContext)
  // Candidates to run in parallel
  .beginWith(Arrays.asList(plantName1, plantName2, plantName3))
  // Dependent work (only runs after all previous work in chain)
  .then(cache)
  .then(upload)
  // Call enqueue to kick things off
  .enqueue();

输入合并器

当您链接 OneTimeWorkRequest 实例时,父级工作请求的输出将作为子级的输入传入。因此,在上面的示例中,plantName1plantName2 和 plantName3 的输出将作为 cache 请求的输入传入。

为了管理来自多个父级工作请求的输入,WorkManager 使用 InputMerger。

WorkManager 提供两种不同类型的 InputMerger

  • OverwritingInputMerger会尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的键。
  • ArrayCreatingInputMerger会尝试合并输入,并在必要时创建数组。

OverwritingInputMerger

OverwritingInputMerger 是默认的合并方法。如果合并过程中存在键冲突,键的最新值将覆盖生成的输出数据中的所有先前版本。

例如,如果每种植物的输入都有一个与其各自变量名称("plantName1""plantName2" 和 "plantName3")匹配的键,传递给 cache 工作器的数据将具有三个键值对。

chaining-overwriting-merger-example.png 如果存在冲突,那么最后一个工作器将在争用中“取胜”,其值将传递给 cache

chaining-overwriting-merger-conflict.png 由于工作请求是并行运行的,因此无法保证其运行顺序。在上面的示例中,plantName1 可以保留值 "tulip" 或 "elm",具体取决于最后写入的是哪个值。如果有可能存在键冲突,并且您需要在合并器中保留所有输出数据,那么 ArrayCreatingInputMerger 可能是更好的选择。

ArrayCreatingInputMerger

对于上面的示例,假设我们要保留所有植物名称工作器的输出,则应使用 ArrayCreatingInputMerger

OneTimeWorkRequest cache = new OneTimeWorkRequest.Builder(PlantWorker.class)
      .setInputMerger(ArrayCreatingInputMerger.class)
      .setConstraints(constraints)
      .build();

ArrayCreatingInputMerger 将每个键与数组配对。如果每个键都是唯一的,您会得到一系列一元数组。

chaining-array-merger-example.png 如果存在任何键冲突,那么所有对应的值会分组到一个数组中。

chaining-array-merger-conflict.png

链接和工作状态

只要工作成功完成(即,返回 Result.success()),OneTimeWorkRequest 链便会按顺序执行。运行时,工作请求可能会失败或被取消,这会对依存工作请求产生下游影响。

当第一个 OneTimeWorkRequest 被加入工作请求链队列时,所有后续工作请求会被屏蔽,直到第一个工作请求的工作完成为止。

chaining-enqueued-all-blocked.png

在加入队列且满足所有工作约束后,第一个工作请求开始运行。如果工作在根 OneTimeWorkRequest 或 List<OneTimeWorkRequest> 中成功完成(即返回 Result.success()),系统会将下一组依存工作请求加入队列。

chaining-enqueued-in-progress.png

只要每个工作请求都成功完成,工作请求链中的剩余工作请求就会遵循相同的运行模式,直到链中的所有工作都完成为止。这是最简单的用例,通常也是首选用例,但处理错误状态同样重要。

如果在工作器处理工作请求时出现错误,您可以根据您定义的退避政策来重试该请求。重试请求链中的某个请求意味着,系统将使用提供给该请求的输入数据仅对该请求进行重试。并行运行的所有其他作业均不会受到影响。

chaining-enqueued-retry.png

如果该重试政策未定义或已用尽,或者您以其他方式已达到 OneTimeWorkRequest 返回 Result.failure() 的某种状态,该工作请求和所有依存工作请求都会被标记为 FAILED.

chaining-enqueued-failed.png

OneTimeWorkRequest 被取消时遵循相同的逻辑。任何依存工作请求也会被标记为 CANCELLED,并且无法执行其工作。

chaining-enqueued-cancelled.png

请注意,如果要向已失败或已取消工作请求的链附加更多工作请求,新附加的工作请求也会分别标记为 FAILED 或 CANCELLED。如果您想扩展现有链的工作,需要ExistingWorkPolicy中的 APPEND_OR_REPLACE

下面我们验证下工作状态: (一)

        Data data1 = new Data.Builder().putString("key", "1").build();
       OneTimeWorkRequest uploadWorkRequest1 =
               new OneTimeWorkRequest.Builder(UploadWorker.class)
                      .setInputData(data1)
                      .addTag("work")
                       // Additional configuration
                      .build();

       Data data2 = new Data.Builder().putString("key", "2").build();
       OneTimeWorkRequest uploadWorkRequest2 =
               new OneTimeWorkRequest.Builder(UploadWorker.class)
                      .setInputData(data2)
                      .addTag("work")
                       // Additional configuration
                      .build();

       Data data3 = new Data.Builder().putString("key", "3").build();
       OneTimeWorkRequest uploadWorkRequest3 =
               new OneTimeWorkRequest.Builder(UploadWorker.class)
                      .setInputData(data3)
                      .addTag("work_work")
                       // Additional configuration
                      .build();
       Log.d(TAG, "WorkRequest 3 id is " + uploadWorkRequest3.getId());

       Data data4 = new Data.Builder().putString("key", "4").build();
       OneTimeWorkRequest uploadWorkRequest4 =
               new OneTimeWorkRequest.Builder(UploadWorker.class)
                      .setInputData(data4)
                      .addTag("work_work")
                       // Additional configuration
                      .build();
       Log.d(TAG, "WorkRequest 4 id is " + uploadWorkRequest4.getId());

       WorkManager workManager = WorkManager.getInstance(MainActivity.this);

       workManager.beginWith(Arrays.asList(uploadWorkRequest1,uploadWorkRequest2))
              .then(uploadWorkRequest3)
              .then(uploadWorkRequest4)
              .enqueue();

代码如上,打印出来的Log如下:

2021-01-12 23:17:05.106 30630-30665/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 1
2021-01-12 23:17:05.110 30630-30666/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 2
2021-01-12 23:17:05.194 30630-30669/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 3
2021-01-12 23:17:05.222 30630-30670/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 4

通过上面的Log发现如下:

①:的确先执行的1和2,然后执行的3再是4。

(二)

如果仅仅把OneTimeWorkRequest1添加一个延时,把代码变成如下:

        OneTimeWorkRequest uploadWorkRequest1 =
               new OneTimeWorkRequest.Builder(UploadWorker.class)
                      .setInitialDelay(10, TimeUnit.SECONDS)
                      .setInputData(data1)
                      .addTag("work")
                       // Additional configuration
                      .build();
.................

执行的Log如下:

2021-01-12 23:24:02.731 31097-31130/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 2
2021-01-12 23:24:12.652 31097-31149/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 1
2021-01-12 23:24:12.730 31097-31150/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 3
2021-01-12 23:24:12.763 31097-31151/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 4

通过上面的Log:

①:发现2已经先执行了,这说明1、2的执行没有先后顺序。

②:3和4等1执行完毕之后才执行。

(三)

再次修正上面的代码,在把Worker提交给系统的下一行,添加对于OneTimeWorkRequest1的cancel

................
       workManager.beginWith(Arrays.asList(uploadWorkRequest1,uploadWorkRequest2))
              .then(uploadWorkRequest3)
              .then(uploadWorkRequest4)
              .enqueue();

       workManager.cancelWorkById(uploadWorkRequest1.getId());

执行的Log如下:

2021-01-12 23:29:10.977 31585-31618/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 2

通过Log发现:

①:当把OneTimeWorkRequest1给cancel了,在工作链后面执行的OneTimeWorkRequest3和OneTimeWorkRequest4也被cancel掉了。

(四)

接下来我想验证下ExistingWorkPolicy.APPEND_OR_REPLACE.

修改代码如下:

..............
       WorkContinuation chain1 = workManager.beginUniqueWork("work&work", ExistingWorkPolicy.APPEND_OR_REPLACE,
Arrays.asList(uploadWorkRequest1,uploadWorkRequest2));

       WorkContinuation chain2 = workManager.beginUniqueWork("work&work", ExistingWorkPolicy.APPEND_OR_REPLACE,
Arrays.asList(uploadWorkRequest3,uploadWorkRequest4))
          .then(uploadWorkRequest5);

       chain1.enqueue();
       chain2.enqueue();

        workManager.cancelWorkById(uploadWorkRequest1.getId());

执行的Log如下:

2021-01-13 03:52:08.084 10457-10457/com.example.myapplication D/MainActivity: WorkRequest 3 is cancelled
2021-01-13 03:52:08.084 10457-10457/com.example.myapplication D/MainActivity: WorkRequest 2 is enqueued
2021-01-13 03:52:08.084 10457-10457/com.example.myapplication D/MainActivity: WorkRequest 4 is cancelled
2021-01-13 03:52:08.085 10457-10457/com.example.myapplication D/MainActivity: WorkRequest 1 is cancelled
2021-01-13 03:52:08.094 10457-10491/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 2
2021-01-13 03:52:08.123 10457-10457/com.example.myapplication D/MainActivity: WorkRequest 2 is succeeded

通过上面的Log发现:

①:由于先执行了WorkContinuaton1, 然后又把WorkContinuaton1中的WorkRequest1给Cancel了, 所以后续的WorkRequest3、4都受影响, 被cancel了。

②: 而WorkRequest2 由于APPEND_OR_REPLACE的影响, 能够正常执行。

(五)

如果把chain1.enqueue()和chain2.enqueue()的执行顺序调换下. 再次执行.

Log如下:

2021-01-13 04:04:33.951 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 1 is cancelled
2021-01-13 04:04:33.951 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 3 is enqueued
2021-01-13 04:04:33.951 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 4 is enqueued
2021-01-13 04:04:33.962 10938-11023/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 3
2021-01-13 04:04:33.968 10938-11024/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 4
2021-01-13 04:04:34.011 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 3 is succeeded
2021-01-13 04:04:34.011 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 5 is enqueued
2021-01-13 04:04:34.011 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 4 is succeeded
2021-01-13 04:04:34.020 10938-11026/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 5
2021-01-13 04:04:34.046 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 5 is running
2021-01-13 04:04:34.057 10938-11027/com.example.myapplication D/MainActivity: UploadWorker doWork and key is 2
2021-01-13 04:04:34.057 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 5 is succeeded
2021-01-13 04:04:34.057 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 2 is enqueued
2021-01-13 04:04:34.074 10938-10938/com.example.myapplication D/MainActivity: WorkRequest 2 is succeeded

通过上面的Log发现:

①: 在WorkContinuation2先执行的情况下, WorkContinuation2这边的工作链不受到WorkRequest1的cancel的影响。

②:WorkContinuation1的WorkRequest2因为APPEND_OR_REPLACE, 不受到WorkRequest1的cancel影响。

※上面的示例(四)和(五)在实际使用过程中需要特别注意, 在使用多个工作链的时候, 需要注意前一个执行的工作链的状态对后执行的工作链的影响。

0 个评论

要回复文章请先登录注册