WorkManager 高级进阶
WorkManager 简化了精密任务请求的设定和安排,您可以在如下情境中使用其 API:
- 任务链式请求(Chained sequence):按照指定顺序执行任务。
- 唯一命名序列(Unique named sequence):在应用同时执行两个相同命名的序列时做决断。
- 传递并返回值 的任务,包括链式任务中每个任务都将参数传给下一个的情况。
链式任务(Chained task)
您的应用可能想要按某个特定的顺序执行一系列任务。WorkManager
允许您创建一个任务序列并将其加入队列,该序列指定了多个任务及其执行的顺序。
例如,假设您的应用有三个 OneTimeWorkRequest
: workA
、workB
和 workC
,且它们必须按这个顺序执行。欲将其加入队列,将第一个任务传入 WorkManager.beginWith(OneTimeWorkRequest)
) 来创建一个序列,该方法返回一个定义了任务序列的 WorkContinuation
对象;接着,使用 WorkContinuation.then(OneTimeWorkRequest)
) 来将剩余的 OneTimeWorkRequest
按顺序加入序列;最后,使用 WorkContinuation.enqueue()
来将整个序列加入队列。
WorkManager.getInstance()
.beginWith(workA)
// 注意:WorkManager.beginWith() 返回一个
// WorkContinuation 对象,因而下面调用的都是
// WorkContinuation 的方法
.then(workB) // 怕您忘了,then() 返回的是一个新的 WorkContinuation 实例
.then(workC)
.enqueue();
WorkManager
会根据每个任务的限制条件,按照请求的顺序执行它们。如果任意一个返回了 Result.failure()
),整个序列就会结束。
您也可以向任意 beginWith(List<OneTimeWorkRequest>)
) 或 then(List<OneTimeWorkRequest>)
) 中传入多个 OneTimeWorkRequest
对象。那样的话,WorkManager
会在执行剩余序列前将它们全部(并行地)执行。例如:
WorkManager.getInstance()
// 首先,(并行地)执行所有 A 任务:
.beginWith(Arrays.asList(workA1, workA2, workA3))
// ……当所有 A 任务都执行结束后,执行一个 B 任务:
.then(workB)
// ……之后,再(按任意顺序)执行 C 任务:
// (译者注:这是因为 C 任务之后没有其他任务了)
.then(Arrays.asList(workC1, workC2))
.enqueue();
您还可以通过 WorkContinuation.combine(List<OneTimeWorkRequest>)
) 方法来并合多个任务链,从而创建更加复杂的序列。例如,假设您想要执行一个如下图所示的序列:
欲设置这样的序列,分别创建两个任务链,再将其并合到第三个中:
WorkContinuation chain1 = WorkManager.getInstance()
.beginWith(workA)
.then(workB);
WorkContinuation chain2 = WorkManager.getInstance()
.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
按顺序执行每个子链(subchain)时,它并不保证 chain1 中的任务是否会和 chain2 中的重叠。例如,workB 可能会在 workC 之前或之后执行,甚至可能同时执行。唯一可以确定的是,每个子链里的任务是按顺序执行的,也即:workB 在 workA 结束之前决不会开始。
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
类的代码则会如下所示:
// 定义 Worker 类:
public class MathWorker extends Worker {
// 定义输入参数的键:
public static final String KEY_X_ARG = "X";
public static final String KEY_Y_ARG = "Y";
public static final String KEY_Z_ARG = "Z";
// ……和输出结果的键:
public static final String KEY_RESULT = "result";
public MathWorker(
@NonNull Context context,
@NonNull WorkerParameters params) {
super(context, params);
}
@Override
public Result doWork() {
// 获取输入参数(并指定缺省值):
int x = getInputData().getInt(KEY_X_ARG, 0);
int y = getInputData().getInt(KEY_Y_ARG, 0);
int z = getInputData().getInt(KEY_Z_ARG, 0);
// ……执行数学计算……
int result = myCrazyMathFunction(x, y, z);
//……设置好输出结果,我们就大功告成了!
Data output = new Data.Builder()
.putInt(KEY_RESULT, result)
.build();
return Result.success(output);
}
}
欲创建任务并传入参数,您可以使用如下的代码:
// 创建 Data 对象:
Data myData = new Data.Builder()
// 我们需要传入三个整数:X、Y 和 Z
.putInt(KEY_X_ARG, 42)
.putInt(KEY_Y_ARG, 421)
.putInt(KEY_Z_ARG, 8675309)
// …… 并创建实际的 Data 对象:
.build();
// ……然后使用这些参数来创建一个 OneTimeWorkRequest 并将其加入队列
OneTimeWorkRequest mathWork = new OneTimeWorkRequest.Builder(MathWorker.class)
.setInputData(myData)
.build();
WorkManager.getInstance().enqueue(mathWork);
返回的值可以在任务的 WorkInfo
中获取:
WorkManager.getInstance().getWorkInfoByIdLiveData(mathWork.getId())
.observe(lifecycleOwner, info -> {
if (info != null && info.getState().isFinished()) {
int myResult = info.getOutputData().getInt(KEY_RESULT,
myDefaultValue));
// …… 用输出结果做点什么 ……
}
});
如果您把任务串成链,那么上一个任务的输出就可以用来作为下一个任务的输入。
如果该任务链是两个 OneTimeWorkRequest
的简单连接,那么第一个任务调用 Result.success(Data)
) 来设置结果,而第二个任务则通过 getInputData()
来获取该结果。
如果该任务链结构复杂,例如多个任务都将结果输出到同一个任务中,那么您可以在 OneTimeWorkRequest.Builder
中定义一个 InputMerger
用于指定不同任务返回有相同键的结果时应当采取什么操作。