之前讨论过的任务,无论是单次任务,还是周期性任务,都是单一的任务项执行。如果我们要多个任务项同时进行,或者按一定顺序执行,就需要用到链式任务。
任务链
任务链的启动,需要一个核心类:WorkContinuation
WorkContinuation
1 |
|
上述四个方法,前三个都好理解,第四个到底是什么意思呢?
1 |
|
所以,才会有“前置条件”(prerequisites)的说法。
那么,如何获到一个WorkContinuation呢?
构造与执行
获取一个WorkContinuation很简单,WorkManager里面的的”begin*
“方法族就是干这个的:
1 |
|
上面是其中两个简单的方法,直接传入OneTimeWorkRequest
任务(或列表)就行了。
来测试一下,目的是顺序执行多个任务。DelayWorker增加时间参数,用以控制不同的任务执行时间。
1 |
|
看看结果:
1 |
|
四个任务如愿顺序执行了,执行时间依次为:3秒(默认),5秒,1.5秒,1秒。
fail任务处理
前面的第四个任务,用了一个FailedWorker
,它执行后返回是Result.failure()
,即执行失败。前文的结果,好像并没有什么影响。下面,把它提到第二位置,再添加状态监听:
1 |
|
执行结果:
1 |
|
从日志可以清楚地看到,最开始的时候,除第一个任务为ENQUEUED,其他都是BLOCKED阻塞状态。第一个任务成功执行状态变化:ENQUEUED -> RUNNING -> SUCCEEDED。
第一个任务SUCCEEDED的同时,第二个任务变为ENQUEUED准备执行,接下来,第二个任务经历 RUNNING -> FAILED,这导致剩下两个任务也同时从 BLOCKED -> FAILED。
一旦链式任务有执行失败的,后续任务将全部不执行而以失败处理,这一点,官方API文档说得很明白:
New items depend on the successful completion of all previously added {@link OneTimeWorkRequest}s.
combine的使用
前面介绍combine
方法的时候,已经简单说明了它的用途,下面就来实操一下。
案例
在已有的3个正常request的基础上,再增加request5和request6,需要执行以下调用顺序:
- request(3秒) -> request2(5秒):序列一
- request3(1.5秒) -> request5(2秒):序列二
- 1和2两个执行链同时执行
- 3中所有执行完毕后,执行request6(1秒)
这其实是完全和API的example一致的
1 |
|
执行结果很长,我们直接在每一步后面加上分析说明:
1 |
|
上面日志的流程说明,已经很清楚了。但有疑问,为什么总共5个request,WorkInfo
却有6个?可以看到,在第六轮的时候,任务6d66d32a-f416-447a-8b9b-8953631e1896进入ENQUEUED,再到第七轮,没有经过RUNNING,该任务直接进入了SUCCEEDED。它并不是5个任务之一,那它是什么?其实可以大胆猜测,它就是用于保证request6等待序列一、序列二完全执行完毕才执行的“包裹任务”,毕竟,combine的返回结果不也是一个WorkContinuation
吗?
combine源码分析
前面提到的“包裹任务”,真如猜测吗?还是来看源码比较放心。
combine
方法最终会调到WorkContinuationImpl
的combineInternal
方法:
1 |
|
构造WorkContinuationImpl
对象的时候,用到了combinedWork
任务请求、全部任务列表。
1 |
|
其实上述传入的work
参数列表,只有combineWork
一个,而该request指向的CombineContinuationsWorker,就是用于combine任务列表的。
再来看看监听的调用也到了WorkContinuationImpl
里:
1 |
|
这个mAllIds
,不就是前面提到的吗,它除了包括所有的任务id外,确实还包括了combine需要用到的一个(或以上)的combine任务(包裹任务)id
failure处理
如果combine的任务列表里,有执行失败的任务,将怎么影响整个任务序列呢?
1 |
|
上述任务描述为:
- request2(5秒) -> request(3秒):序列一
- request4(3秒) -> request3(1.5秒):序列二
- 1和2完毕后,request6
结果:
1 |
|
从以上日志分析可以得出以下结论:
- 用then串联的WorkContinuation,保持“一错毁所有”原则,一旦执行失败,后续都失败
- combine的子链的failure会抛给整个combine,导致整个任务组合的failure
- combine的子链相互之间不会影响,即正常执行的子链,将完成全部任务
很好理解,子链的失败,和普通的链式任务一样,一旦失败,依赖它的”包裹任务“自然失败,那么then
后的任务也必然失败。
唯一链
除了前面提到的两个启动链式任务的方法外,还有两个方法:
1 |
|
顾名思义,unique
就意味着,用此方法启动的链式任务,在应用中是唯一的。
ExistingWorkPolicy
是一个枚举类,提供了当遭遇到workName冲突时的不同处理策略
1 |
|
现在使用REPLACE策略,连续启动两个同名唯一链。
1 |
|
执行结果:
1 |
|
居然……crash了……
原因是,第二次启动任务时,因为是REPLACE,所以第一个被cancel掉,但它不会收到一个CANCLED状态,而是直接收到一个空的WorkInfo。所以崩溃来了。好吧,改改代码,并加更详细的标志信息
1 |
|
结果:
1 |
|
虽然第一个唯一任务被取消了,但是已经启动的运行过程还是正常执行完成了,只是其状态已经不被WorkManager所关注,所以没有状态回调。
那么多于一个任务项的chain呢?
1 |
|
结果与分析:
1 |
|
如日志分析,符合REPLACE的策略行为描述。改成APPEND试试:
1 |
|
可以看到,第二次的chain(3->4)按序在1->2后执行了。
唯一链的监听
上面的监听其实略复杂,WorkManager
有提供针对唯一链的监听,也是之前讨论监听的时候忽略掉的,因为当时还没用上唯一链。
调用getWorkInfosForUniqueWorkLiveData
统一监听,KEEP策略下,快速启动两次同名唯一链:
1 |
|
结果:
1 |
|
如预期,KEEP模式下,原来任务继续执行至完毕。
非链式唯一性任务
别以为只有链式任务才有unique版本,一般任务也是有的,这里就顺带说一下
1 |
|
单次任务、周期性任务,都可以添加唯一性任务。注意,周期性任务的策略是周期性策略ExistingPeriodicWorkPolicy
,仅支持REPLACE和KEEP
1 |
|
小结
链式任务给任务的组合提供了便捷的API,不仅可以顺序连接任务,还能同时执行任务(combine就是干这事的)。而唯一链更提供了复杂的控制策略,使得任务执行的自定义更丰富强大。