WorkManager :工作链
工作链
工作链也是WorkManager的一个非常重要的功能。
你可以使用WorkManager创建工作链并将其加入队列。工作链用于指定多个依存任务并定义这些任务的运行顺序。当需要以特定顺序运行多个任务时,此功能尤其有用。
例如,假设您的应用有三个 OneTimeWorkRequest对象:workA
、workB
和 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();
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();
您可以使用 WorkContinuation.combine(List)方法联接多个任务链来创建更为复杂的序列。例如,假设您要运行像这样的序列:
如需设置该序列,请创建两个单独的链,然后将它们联接成第三个链:
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
实例时,父级工作请求的输出将作为子级的输入传入。因此,在上面的示例中,plantName1
、plantName2
和 plantName3
的输出将作为 cache
请求的输入传入。
为了管理来自多个父级工作请求的输入,WorkManager 使用 InputMerger。
WorkManager 提供两种不同类型的 InputMerger
:
- OverwritingInputMerger会尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的键。
- ArrayCreatingInputMerger会尝试合并输入,并在必要时创建数组。
OverwritingInputMerger
OverwritingInputMerger
是默认的合并方法。如果合并过程中存在键冲突,键的最新值将覆盖生成的输出数据中的所有先前版本。
例如,如果每种植物的输入都有一个与其各自变量名称("plantName1"
、"plantName2"
和 "plantName3"
)匹配的键,传递给 cache
工作器的数据将具有三个键值对。
如果存在冲突,那么最后一个工作器将在争用中“取胜”,其值将传递给 cache
。
由于工作请求是并行运行的,因此无法保证其运行顺序。在上面的示例中,plantName1
可以保留值 "tulip"
或 "elm"
,具体取决于最后写入的是哪个值。如果有可能存在键冲突,并且您需要在合并器中保留所有输出数据,那么 ArrayCreatingInputMerger
可能是更好的选择。
ArrayCreatingInputMerger
对于上面的示例,假设我们要保留所有植物名称工作器的输出,则应使用 ArrayCreatingInputMerger
。
OneTimeWorkRequest cache = new OneTimeWorkRequest.Builder(PlantWorker.class)
.setInputMerger(ArrayCreatingInputMerger.class)
.setConstraints(constraints)
.build();
ArrayCreatingInputMerger
将每个键与数组配对。如果每个键都是唯一的,您会得到一系列一元数组。
如果存在任何键冲突,那么所有对应的值会分组到一个数组中。
链接和工作状态
只要工作成功完成(即,返回 Result.success()
),OneTimeWorkRequest
链便会按顺序执行。运行时,工作请求可能会失败或被取消,这会对依存工作请求产生下游影响。
当第一个 OneTimeWorkRequest
被加入工作请求链队列时,所有后续工作请求会被屏蔽,直到第一个工作请求的工作完成为止。
在加入队列且满足所有工作约束后,第一个工作请求开始运行。如果工作在根 OneTimeWorkRequest
或 List<OneTimeWorkRequest>
中成功完成(即返回 Result.success()
),系统会将下一组依存工作请求加入队列。
只要每个工作请求都成功完成,工作请求链中的剩余工作请求就会遵循相同的运行模式,直到链中的所有工作都完成为止。这是最简单的用例,通常也是首选用例,但处理错误状态同样重要。
如果在工作器处理工作请求时出现错误,您可以根据您定义的退避政策来重试该请求。重试请求链中的某个请求意味着,系统将使用提供给该请求的输入数据仅对该请求进行重试。并行运行的所有其他作业均不会受到影响。
如果该重试政策未定义或已用尽,或者您以其他方式已达到 OneTimeWorkRequest
返回 Result.failure()
的某种状态,该工作请求和所有依存工作请求都会被标记为 FAILED.
OneTimeWorkRequest
被取消时遵循相同的逻辑。任何依存工作请求也会被标记为 CANCELLED
,并且无法执行其工作。
请注意,如果要向已失败或已取消工作请求的链附加更多工作请求,新附加的工作请求也会分别标记为 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影响。
※上面的示例(四)和(五)在实际使用过程中需要特别注意, 在使用多个工作链的时候, 需要注意前一个执行的工作链的状态对后执行的工作链的影响。