之前讨论过的任务,无论是单次任务,还是周期性任务,都是单一的任务项执行。如果我们要多个任务项同时进行,或者按一定顺序执行,就需要用到链式任务。
任务链
任务链的启动,需要一个核心类: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就是干这事的)。而唯一链更提供了复杂的控制策略,使得任务执行的自定义更丰富强大。