文章目录
1. 使用WorkManager调度任务
WorkManager API可以方便地指定可延迟的异步任务以及何时运行它们。这些API允许您创建任务并将其交给WorkManager立即运行或在适当的时机运行。
WorkManager根据设备API级别和应用程序状态等因素选择适当的方法来运行您的任务。如果WorkManager在应用程序运行时执行您的任务之一,那么WorkManager可以在应用程序的进程中的新线程中运行您的任务。如果您的应用程序没有运行,WorkManager会选择适当的方式来调度后台任务——这取决于设备API级别和包含的依赖,WorkManager可以使用JobScheduler、Firebase JobDispatcher或AlarmManager。您不需要编写设备逻辑来确定设备具有什么功能,并选择合适的API;相反,您可以将任务交给WorkManager,让它选择最佳选项。
注意:WorkManager用于需要保证即使应用程序退出系统也会运行它们的任务,比如将应用程序数据上传到服务器。它不是用于进程内后台工作,如果应用程序进程消失了,可以安全地终止;对于这样的情况,我们建议使用线程池。
1.1 主题
使用WorkManager在您选择的环境下调度单个任务,或者以指定的间隔重复运行任务。
1.2 WorkManager高级功能
设置链式任务序列,设置传递和返回值的任务,并设置命名的、独特的工作序列。
1.3 附加资源
WorkManager是Android Jetpack架构组件。在Sunflower演示应用程序中使用它。
2. WorkManager基础
使用WorkManager,您可以轻松地设置一个任务并将其交给系统,该任务可以在您指定的条件下运行。
这个概述涵盖了最基本的WorkManager特性。在这个页面中,您将学习如何设置任务,指定它应该运行的条件,并将其交给系统。您还将学习如何设置重复作业。
有关更高级的WorkManager特性的信息,如作业链接以及传递和返回值,请参阅WorkManager高级特性。还有更多可用的特性;对于详细信息,请参阅WorkManager参考文档。
注意:要将WorkManager库导入到Android项目中,请参见将组件添加到项目中。
2.1 类与概念
WorkManager API使用了几个不同的类。在某些情况下,您需要对API类中的一个子类进行分类。
这些是最重要的WorkManager类:
Worker:指定需要执行的任务。WorkManager APIs包括抽象Worker类。您继承此类并在此处执行工作。
WorkRequest:表示一个单独的任务。至少,WorkRequest对象指定哪个Worker类应该执行任务。但是,您还可以向WorkRequest对象添加细节,指定任务应该在哪些环境下运行。每个WorkRequest都有一个自动生成的惟一ID;可以使用该ID执行诸如取消队列中的任务或获取任务的状态之类的操作。WorkRequest是一个抽象类;在代码中,您将使用某个子类,OneTimeWorkRequest或PeriodicWorkRequest。
WorkRequest.Builder:创建WorkRequest对象的帮助类。再次,您将使用一个子类,OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder。
Constraints:指定限制任务什么时候运行(例如,“仅当设备连接到网络时”)。你通过创建Constraints.Builder创建 Constraints对象。在创建WorkRequest之前,传递Constraints 对象给WorkRequest.Builder。
WorkManager:对工作请求进行排队和管理。您将WorkRequest对象传递给WorkManager来执行任务。WorkManager以分散系统资源负载的方式调度任务,同时遵守您指定的约束。
WorkStatus:包含有关特定任务的信息。WorkManager为每个WorkRequest对象提供LiveData。LiveData保存一个WorkStatus对象;通过观察LiveData,您可以确定当前任务的状态,并在任务完成后获得到返回值。
2.2 典型工作流
假设你正在编写一个photo library app,并且该应用程序需要周期性地压缩其存储的图像。您希望使用WorkMeaveAPI来安排图像压缩。在这种情况下,您并不特别关心压缩什么时候发生;您想设置任务并忘记它。
首先,您将定义Worker类,并重写它的doWork()方法。您的worker类指定如何执行该操作,但没有任何有关任务何时运行的信息。
KOTLIN
class CompressWorker : Worker() {
override fun doWork(): Result {
// Do the work here--in this case, compress the stored images.
// In this example no parameters are passed; the task is
// assumed to be "compress the whole library."
myCompress()
// Indicate success or failure with your return value:
return Result.SUCCESS
// (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.)
}
}
JAVA
public class CompressWorker extends Worker {
@Override
public Worker.Result doWork() {
// Do the work here--in this case, compress the stored images.
// In this example no parameters are passed; the task is
// assumed to be "compress the whole library."
myCompress();
// Indicate success or failure with your return value:
return Result.SUCCESS;
// (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.)
}
}
接下来,基于Worker创建OneTimeWorkRequest对象,然后使用WorkManager将任务排到队列中:
KOTLIN
val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>().build()
WorkManager.getInstance().enqueue(compressionWork)
JAVA
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(CompressWorker.class)
.build();
WorkManager.getInstance().enqueue(compressionWork);
WorkManager选择适当的时间来运行任务,平衡诸如系统负载、设备是否插入,等等。在大多数情况下,如果不指定任何约束,WorkManager立即运行您的任务。如果需要检查任务状态,可以通过获取适当的LiveData句柄来获得WorkStatus对象。例如,如果您想检查任务是否已完成,您可以使用这样的代码:
KOTLIN
WorkManager.getInstance().getStatusById(compressionWork.id)
.observe(lifecycleOwner, Observer { workStatus ->
// Do something with the status
if (workStatus != null && workStatus.state.isFinished) {
// ...
}
})
JAVA
WorkManager.getInstance().getStatusById(compressionWork.getId())
.observe(lifecycleOwner, workStatus -> {
// Do something with the status
if (workStatus != null && workStatus.getState().isFinished()) {
// ...
}
});
有关使用LiveData的更多信息,请参见LiveData概述。
2.3 任务约束
如果希望,可以指定任务运行时的约束。例如,您可能希望指定该任务只在设备空闲时运行,并连接到电源。在这种情况下,您需要创建一个OneTimeWorkRequest.Builder对象,并使用该生成器创建实际的OneTimeWorkRequest:
KOTLIN
// Create a Constraints object that defines when the task should run
val myConstraints = Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
// Many other constraints are available, see the
// Constraints.Builder reference
.build()
// ...then create a OneTimeWorkRequest that uses those constraints
val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>()
.setConstraints(myConstraints)
.build()
JAVA
// Create a Constraints object that defines when the task should run
Constraints myConstraints = new Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
// Many other constraints are available, see the
// Constraints.Builder reference
.build();
// ...then create a OneTimeWorkRequest that uses those constraints
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(CompressWorker.class)
.setConstraints(myConstraints)
.build();
然后像以前一样将新的OneTimeWorkRequest请求对象传递给WorkManager.enqueue()。WorkManager在查找运行任务的时间时会考虑约束。
2.4 取消任务
您可以在任务加入队列后取消任务。要取消任务,您需要它的工作ID,您可以从WorkRequest对象中获取它。例如,下面的代码取消了前一节的压缩工作请求:
KOTLIN
val compressionWorkId:UUID = compressionWork.getId()
WorkManager.getInstance().cancelWorkById(compressionWorkId)
JAVA
UUID compressionWorkId = compressionWork.getId();
WorkManager.getInstance().cancelWorkById(compressionWorkId);
WorkManager尽最大努力取消任务,但这样存在很大的不确定性——当您试图取消任务时,任务可能已经运行或完成。WorkManager还提供方法,以取消唯一工作序列中的所有任务,或者取消具有指定标记的所有任务,这也是尽最大努力取消任务的基础上。
2.5 Tagged work
您可以通过将标记字符串分配给任何WorkRequest对象来对任务进行逻辑分组。若要设置标签,请调用WorkRequest.Builder.addTag(),例如:
KOTLIN
val cacheCleanupTask =
OneTimeWorkRequestBuilder<MyCacheCleanupWorker>()
.setConstraints(myConstraints)
.addTag("cleanup")
.build()
JAVA
OneTimeWorkRequest cacheCleanupTask =
new OneTimeWorkRequest.Builder(MyCacheCleanupWorker.class)
.setConstraints(myConstraints)
.addTag("cleanup")
.build();
WorkManager类提供了几种实用方法,允许您使用特定标记操作所有任务。例如,WorkManager.cancelAllWorkByTag()取消具有特定标记的所有任务,WorkManager.getStatusesByTag()返回具有该标记的所有任务的WorkStatus列表。
2.6 周期性任务
您可能有一个需要重复执行的任务。例如,照片管理器APP压缩照片可能不止一次。更有可能的是,它会经常检查其共享照片,并查看是否有新的或更改的图像需要压缩。这个重复的任务可以压缩它找到的图像,或者,当它找到需要压缩的图像时,它可以启动新的“压缩此图像”任务。
要创建循环任务,请使用PeriodicWorkRequest.Builder
类创建PeriodicWorkRequest
对象,然后以与OneTimeWorkRequest
对象相同的方式将PeriodicWorkRequest
加入队列。例如,假设我们定义了一个PhotoCheckWorker
类来识别需要压缩的图像。如果您想每12小时运行一次清单任务,您将创建一个像这样的PeriodicWorkRequest
对象:
KOTLIN
val photoCheckBuilder =
PeriodicWorkRequestBuilder<PhotoCheckWorker>(12, TimeUnit.HOURS)
// ...if you want, you can apply constraints to the builder here...
// Create the actual work object:
val photoCheckWork = photoCheckBuilder.build()
// Then enqueue the recurring task:
WorkManager.getInstance().enqueue(photoCheckWork)
JAVA
new PeriodicWorkRequest.Builder photoCheckBuilder =
new PeriodicWorkRequest.Builder(PhotoCheckWorker.class, 12,
TimeUnit.HOURS);
// ...if you want, you can apply constraints to the builder here...
// Create the actual work object:
PeriodicWorkRequest photoCheckWork = photoCheckBuilder.build();
// Then enqueue the recurring task:
WorkManager.getInstance().enqueue(photoCheckWork);
WorkManager
试图在您请求的时间间隔内运行您的任务,但要遵守您施加的约束以及其它要求。
3. WorkManager高级主题
WorkManager使建立和预约复杂的任务请求变得容易。您可以使用API来实现这样的场景:
3.1 Chained tasks
应用程序可能需要按特定顺序运行多个任务。WorkManager允许您创建多个任务并将其加入工作序列中,并指定它们应该运行的顺序。
例如,假设应用程序有三个OneTimeWorkRequest对象:WorkA、WorkB和WorkC。任务必须按该顺序运行。要使它们入队,使用WorkManager.beginWith()方法创建一个序列,传递第一个OneTimeWorkRequest对象;该方法返回一个WorkContinuation对象,该对象定义一系列任务。然后按顺序使用WorkContinuation.then()添加剩余的OneTimeWorkRequest对象,最后使用WorkContinuation.enqueue()对整个序列进行入队:
KOTLIN
WorkManager.getInstance()
.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()
JAVA
WorkManager.getInstance()
.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按要求的顺序运行任务。如果任何任务返回Worker.Result.FAILURE,则整个序列结束。
您还可以将多个OneTimeWorkRequest对象传递给任何beginWith()和.then()调用。如果将几个OneTimeWorkRequest对象传递给一个方法调用,WorkManager在运行序列的其余部分之前(并行地)运行所有这些任务。例如:
KOTLIN
WorkManager.getInstance()
// First, run all the A tasks (in parallel):
.beginWith(workA1, workA2, workA3)
// ...when all A tasks are finished, run the single B task:
.then(workB)
// ...then run the C tasks (in any order):
.then(workC1, workC2)
.enqueue()
JAVA
WorkManager.getInstance()
// First, run all the A tasks (in parallel):
.beginWith(workA1, workA2, workA3)
// ...when all A tasks are finished, run the single B task:
.then(workB)
// ...then run the C tasks (in any order):
.then(workC1, workC2)
.enqueue();
您可以通过使用WorkContinuation.combine()方法将多个链合并来创建更复杂的序列。例如,假设您想运行这样的序列:
图1 可以使用WorkContinuation设置复杂链式任务。
要设置这个序列,创建两个独立的链,然后将它们组合成第三个:
KOTLIN
val chain1 = WorkManager.getInstance()
.beginWith(workA)
.then(workB)
val chain2 = WorkManager.getInstance()
.beginWith(workC)
.then(workD)
val chain3 = WorkContinuation
.combine(chain1, chain2)
.then(workE)
chain3.enqueue()
JAVA
WorkContinuation chain1 = WorkManager.getInstance()
.beginWith(workA)
.then(workB);
WorkContinuation chain2 = WorkManager.getInstance()
.beginWith(workC)
.then(workD);
WorkContinuation chain3 = WorkContinuation
.combine(chain1, chain2)
.then(workE);
chain3.enqueue();
在这种情况下,WorkMeor在工作B之前运行WorkA。它还运行WorkC之前Word。工作和工作完成后,WorkMeor运行WORKE。
注意:虽然WorkManager按顺序运行每个子链,但是不能保证链1中的任务如何与链2中的任务重叠。例如,WorkB可能在workC之前或之后运行,或者它们可能同时运行。唯一的保证是每个子链中的任务将按顺序运行;也就是说,workB直到workA完成后才启动。
存在一些特定情况,有一系列WorkContinuation方法提供短时处理。例如,有一个WorkContinuation.combine(OneTimeWorkRequest,WorkContinuation…)方法,它指示WorkManager完成所有指定的WorkContinuation链,然后以指定的OneTimeWorkRequest结束。有关详细信息,请参见WorkContinuation。
3.2 Unique工作序列
您可以创建一个Unique的工作序列,通过调用beginUniqueWork()而不是beginWith()启动序列。每个唯一的工作序列都有一个名称;WorkManager只允许一次使用一个名称的工作序列。在创建新的唯一工作序列时,指定如果已经存在具有相同名称的未完成序列,则WorkManager应该做什么:
如果您有一个不应该多次排队的任务,那么Unique工作序列可能是有用的。例如,如果您的app需要将其数据同步到网络,则可以将名为“sync”的序列加入队列,并指定如果已经存在具有该名称的序列,则应忽略新任务。如果你需要逐步建立一个长的任务链,那么Unique工作序列也是有用的。例如,一个图片编辑应用程序可以让用户撤消一长串的动作。每一个撤消操作可能需要一段时间,但它们必须按正确的顺序执行。在这种情况下,应用程序可以创建一个"undo"链,并根据需要将每个撤销操作附加到链上。
3.3 输入参数和返回值
为了获得更大的灵活性,可以将参数传递给任务并使任务返回结果。传递和返回的值是键值对。若要将参数传递给任务,请在创建WorkRequest对象之前调用WorkRequest.Builder.setInputData()方法。该方法使用Data.Builder创建并持有数据对象。Work类可以通过调用Worker.getInputData()来访问这些参数。为了输出返回值,任务调用Worker.setOutputData(),它接受一个Data对象;您可以通过观察任务的LiveData来获得输出。
例如,假设您有一个Worker类,执行耗时的计算。下面的代码显示了Worker类的外观:
KOTLIN
// Define the parameter keys:
const val KEY_X_ARG = "X"
const val KEY_Y_ARG = "Y"
const val KEY_Z_ARG = "Z"
// ...and the result key:
const val KEY_RESULT = "result"
// Define the Worker class:
class MathWorker : Worker() {
override fun doWork(): Result {
val x = inputData.getInt(KEY_X_ARG, 0)
val y = inputData.getInt(KEY_Y_ARG, 0)
val z = inputData.getInt(KEY_Z_ARG, 0)
// ...do the math...
val result = myCrazyMathFunction(x, y, z);
//...set the output, and we're done!
val output: Data = mapOf(KEY_RESULT to result).toWorkData()
setOutputData(output)
return Result.SUCCESS
}
}
JAVA
// Define the Worker class:
public class MathWorker extends Worker {
// Define the parameter keys:
public static final String KEY_X_ARG = "X";
public static final String KEY_Y_ARG = "Y";
public static final String KEY_Z_ARG = "Z";
// ...and the result key:
public static final String KEY_RESULT = "result";
@Override
public Worker.Result doWork() {
// Fetch the arguments (and specify default values):
int x = getInputData().getInt(KEY_X_ARG, 0);
int y = getInputData().getInt(KEY_Y_ARG, 0);
int z = getInputData().getInt(KEY_Z_ARG, 0);
// ...do the math...
int result = myCrazyMathFunction(x, y, z);
//...set the output, and we're done!
Data output = new Data.Builder()
.putInt(KEY_RESULT, result)
.build();
setOutputData(output);
return Result.SUCCESS;
}
}
要创建work并传递参数,您将使用这样的代码:
KOTLIN
val myData: Data = mapOf("KEY_X_ARG" to 42,
"KEY_Y_ARG" to 421,
"KEY_Z_ARG" to 8675309)
.toWorkData()
// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
.setInputData(myData)
.build()
WorkManager.getInstance().enqueue(mathWork)
JAVA
// Create the Data object:
Data myData = new Data.Builder()
// We need to pass three integers: X, Y, and Z
.putInt(KEY_X_ARG, 42)
.putInt(KEY_Y_ARG, 421)
.putInt(KEY_Z_ARG, 8675309)
// ... and build the actual Data object:
.build();
// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
OneTimeWorkRequest mathWork = new OneTimeWorkRequest.Builder(MathWorker.class)
.setInputData(myData)
.build();
WorkManager.getInstance().enqueue(mathWork);
任务的WorkStatus中返回的值是可用:
KOTLIN
WorkManager.getInstance().getStatusById(mathWork.id)
.observe(this, Observer { status ->
if (status != null && status.state.isFinished) {
val myResult = status.outputData.getInt(KEY_RESULT,
myDefaultValue)
// ... do something with the result ...
}
})
JAVA
WorkManager.getInstance().getStatusById(mathWork.getId())
.observe(lifecycleOwner, status -> {
if (status != null && status.getState().isFinished()) {
int myResult = status.getOutputData().getInt(KEY_RESULT,
myDefaultValue));
// ... do something with the result ...
}
});
如果链任务,则一个任务的输出可用作链中的下一个任务的输入。如果是一个简单的链,只有一个OneTimeWorkRequest,后面跟着另一个OneTimeWorkRequest,第一个任务通过调用setOutputData()返回结果,而下一个任务通过调用getInputData()获取结果。如果链更复杂,例如,因为多个任务都向单个后续任务发送输出,所以可以在OneTimeWorkRequest.Builder上定义InputMerger,以指定在不同的任务返回具有相同键的输出时应该发生什么。
3.4 附加资源
WorkManager是Android Jetpack架构组件。在Sunflower demo app中使用它。