Android 架构组件 -WorkManager
WorkManager 会根据移动设备的 API 级别和应用程序状态等因素选择适当的方式来运行我们的任务,如果 WorkManager 在应用程序运行时执行其中一项任务,则 WorkManager 可以在应用进程中开启一个的新线程中运行这个任务。如果你的应用程序未运行,WorkManager 将选择合适的方式来安排后台任务 - 这取决于设备 API 级别和应用包含的依赖关系,WorkManager 可能使用JobScheduler, Firebase JobDispatcher或AlarmManager。我们完全不需要编写设备逻辑来确定设备具有哪些功能并选择适当的 API,相反,我们可以将任务交给 WorkManager 并让它选择最佳选项。
此外,WorkManager 提供了几个高级功能,例如,我们可以建立一连串的任务,当其中一个任务完成时,WorkManager 会顺序处理下一个任务;我们还可以通过观察任务的 LiveData 来检查任务的状态以及它的返回值,如果你想通过展示 UI 来指示任务的进度,这个特性会非常有用。
依赖 WorkManager
WorkManager 类已经在 androidx.work 包中,但目前依赖于 Support Library 27.1 以及相关的 Arch 组件版本,将来会发布带有 AndroidX 依赖项的 WorkManager 版本。
dependencies {def work_version = "1.0.0-alpha01"
implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin
// optional - Firebase JobDispatcher supportimplementation "android.arch.work:work-firebase:$work_version"
// optional - Test helpersandroidTestImplementation "android.arch.work:work-testing:$work_version"}
类和概念
WorkManager API 使用了几个不同的类,在某些情况下,我们需要继承其中一个 API 类。
下面我们来看看最重要的几个类:
Worker:指定我们需要执行的任务。 WorkManager API 包含一个抽象的 Worker 类,我们需要继承这个类并且在这里执行工作。
WorkRequest:代表一个单独的任务。一个WorkRequest 对象指定哪个 Woker 类应该执行该任务,而且,我们还可以向 WorkRequest 对象添加详细信息,指定任务运行的环境等。每个 WorkRequest 都有一个自动生成的唯一 ID,我们可以使用该 ID 来执行诸如取消排队的任务或获取任务状态等内容。 WorkRequest 是一个抽象类,在代码中,我们需要使用它的直接子类,OneTimeWorkRequest 或 PeriodicWorkRequest.。
WorkRequest.Builder:用于创建
WorkRequest
对象的辅助类,同样,我们要使用它的一个子类,OneTimeWorkRequest.Builder 和 PeriodicWorkRequest.Builder 。Constraints:指定任务在何时运行(例如,“仅在连接到网络时”)。我们可以通过Constraints.Builder 来创建
Constraints
对象,并在创建WorkRequest
之前,将Constraints
对象传递给WorkRequest.Builder
。WorkManager:将
WorkRequest
入队和管理WorkRequest
。我们要将WorkRequest对象传递给 WorkManager ,WorkManager 以这样的方式调度任务,以便分散系统资源的负载,同时遵守我们指定的约束条件。WorkStatus:包含有关特定任务的信息。WorkManager 为每个 WorkRequest 对象提供一个 LiveData,
LiveData
持有一个WorkStatus
对象,通过观察LiveData
,我们可以确定任务的当前状态,并在任务完成后获取返回的任何值。
典型的工作流程
假设我们正在开发一个照片库应用,并且该应用程序需要定期压缩其存储的图像,我们想使用 WorkManager API 调度图像压缩任务,在这种情况下,我们并不需要关心压缩发生在何时,我们只需要设置这个任务然后就可以忘记它。
首先,我们需要定义自己的Worker
类,然后重写此类的 doWork() 方法,我们需要指定Worker
类如何执行这个操作,但是不应该出现任何关于任务在何时运行的信息。
public class CompressWorker extends Worker {@Overridepublic Worker.WorkerResult 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 WorkerResult.SUCCESS;
// (Returning RETRY tells WorkManager to try this task again// later; FAILURE says not to try again.)}}
接下来,我们要创建一个基于此Worker
的OneTimeWorkRequest
对象,然后使用WorkManager
让这个任务入队:
OneTimeWorkRequest compressionWork = new OneTimeWorkRequest.Builder(CompressWorker.class).build();WorkManager.getInstance().enqueue(compressionWork);
WorkManager
会选择适当的时间运行这个任务,平衡诸如系统负载,设备是否插入等考虑因素。在多数情况下,如果我们没有指定任何约束条件,WorkManager
会立即运行我们的任务。如果我们需要检查任务的状态,我们可以通过获取合适的LiveData <WorkStatus>
的句柄来获取WorkStatus
对象。例如,如果我们想检查任务是否完成,可以使用如下代码:
WorkManager.getInstance().getStatusById(compressionWork.getId()).observe(lifecycleOwner, workStatus -> {// Do something with the statusif (workStatus != null && workStatus.getState().isFinished()){ ... }});
任务约束条件
如果我们愿意,我们还可以限制任务运行的时间。例如,我们可能想要指定该任务只在设备闲置并接通电源时运行。在这种情况下,我们需要创建一个OneTimeWorkRequest.Builder
对象,并使用这个构造器创建实际的OneTimeWorkRequest
:
// Create a Constraints that defines when the task should runConstraints 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 constraintsOneTimeWorkRequest compressionWork = new OneTimeWorkRequest.Builder(CompressWorker.class).setConstraints(myConstraints).build();
然后像之前代码一样将新的OneTimeWorkRequest
对象传递给WorkManager.enqueue()
, WorkManager
在查找运行任务的时间时会考虑我们的约束条件。
取消任务
当我们将任务入列后,我们还可以取消这个任务。要取消任务,我们需要这个任务的 Work ID,当然 Work ID 可以从WorkRequest
对象中获取。例如,以下代码将取消上一节中的compressionWork
请求:
UUID compressionWorkId = compressionWork.getId();WorkManager.getInstance().cancelByWorkId(compressionWorkId);
WorkManager
会尽最大努力取消任务,但实质上这是不确定的 - 当我们尝试取消任务时,任务可能已经运行或完成。WorkManager
还提供方法来取消 **唯一工作序列(这个概念在下面会讲到)**中的所有任务,或尽最大努力的取消具有指定标记的所有任务。
高级功能
WorkManager API 的核心功能可以使开发者能够创建简单的、即开即忘的任务,除此之外,API 还提供了高级功能,可以让我们设置更多精准的请求。
重复执行的任务
我们可能需要重复执行一项任务,例如,照片管理应用不会只想压缩其照片一次。更有可能的是,它会希望每隔一段时间检查一次照片,并查看是否有任何新的或改变的图像需要压缩,这个循环任务可以压缩它找到的图像,或者它可以启动新的“压缩图像”任务。
要创建循环任务,要使用PeriodicWorkRequest.Builder类创建一个PeriodicWorkRequest
对象,然后将PeriodicWorkRequest
以与OneTimeWorkRequest
对象相同的方式入列。例如,假如我们定义了一个PhotoCheckWorker
类来识别需要压缩的图像,如果我们想每 12 小时运行一次这个任务,我们可以像下面这样创建一个PeriodicWorkRequest
对象 :
new PeriodicWorkRequest.Builder photoWorkBuilder =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 photoWork = photoWorkBuilder.build();// Then enqueue the recurring task:WorkManager.getInstance().enqueue(invWork);
WorkManager 尝试按照我们请求的时间间隔运行任务,但要受到我们施加的限制和其他要求的限制。
链式任务
有时候我们想让应用程序按照特定的顺序运行多个任务。 WorkManager允许我们创建和排队多个任务的工作序列,以及它们应该以什么顺序运行。
例如,假如我们的应用有三个 OneTimeWorkRequest
对象:workA
, workB
, 和 workC
,这些任务必须按照该顺序执行。要想将它们排队,请使用WorkManager.beginWith() 方法创建一个序列,并传递第一个OneTimeWorkRequest
对象,该方法返回一个WorkContinuation对象,该对象定义了一系列任务。然后依次使用 WorkContinuation.then()添加剩余的OneTimeWorkRequest
对象,最后使用 WorkContinuation.enqueue()排序整个序列:
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.WorkerResult.FAILURE,则整个序列结束。
我们还可以将多个OneTimeWorkRequest
对象传递给beginWith()
和.then()
调用中的任何一个。如果我们将多个OneTimeWorkRequest
对象传递给单个方法调用,那么WorkManager将在运行序列中其余部分任务前运行所有这些任务(并行)。例如:
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来设置复杂的链式任务。
要建立这个序列,先创建两个单独的链,然后将它们连接在一起成为第三个链:
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();
在这种情况下,WorkManager在workB
之前运行workA
,它也在workD
之前运行workC
, WorkB
和workD
都完成后,WorkManager 运行workE
。
注意:虽然WorkManager依次运行每个子链,但不能保证
链1
中的任务与 链 2 中的任务重叠,例如,workB
可能在workC
之前或之后运行,或者它们可能同时运行。唯一可以保证的是每个子链中的任务将按顺序运行,也就是说,workB
在workA
完成之后才开始。
WorkContinuation的方法有许多变体,可以为特定情况提供简要说明。例如,有一个WorkContinuation.combine(OneTimeWorkRequest, WorkContinuation…)
方法,该方法指示 WorkManager完成所有指定的 WorkContinuation链,然后完成指定的OneTimeWorkRequest
。
唯一的工作序列
我们要想创建一个唯一的工作序列,只需调用beginUniqueWork()而不是 beginWith().来开始序列。每个唯一的工作序列都有一个名字,WorkManager
一次只允许一个工作序列使用该名称,当我们创建一个新的唯一工作序列时,如果已经有一个未完成的序列具有相同的名称,则指定WorkManager
应执行的操作:
取消现有的序列并用新序列其替换
保持现有顺序并忽略新的请求
将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务
如果我们有一个不应该多次入队的任务,则唯一工作序列可能很有用。例如,如果我们的应用需要将其数据同步到网络,我们可能会排列一个名为“sync”的序列,并指定如果已经有一个具有该名称的序列,则应该忽略我们的新任务。如果我们需要逐步建立一个长期的任务链,那么唯一的工作序列也会很有用,例如,照片编辑应用可能会让用户撤消一长串的操作,每个撤销操作可能需要一段时间,但必须按正确的顺序执行,在这种情况下,应用程序可以创建一个**“撤消”链**并根据需要将每个撤销操作追加到链中。
标记 Work
我们可以通过将字符串标签指派给任何WorkRequest
对象,将我们的任务按照逻辑分组。要设置标签,请调用WorkRequest.Builder.addTag(),例如:
OneTimeWorkRequest cacheCleanupTask =new OneTimeWorkRequest.Builder(MyCacheCleanupWorker.class).setConstraints(myConstraints).addTag("cleanup").build();
WorkManager类提供了几种实用方法,可让我们使用特定标记对所有任务进行操作。例如,WorkManager.cancelAllWorkByTag()可以取消具有特定标记的所有任务,并且 WorkManager.getStatusesByTag()返回具有该标记的所有任务的所有WorkStatus
的列表。
输入参数和返回值
为了获得更大的灵活性,还可以将参数传递给我们的任务,并让任务返回结果。传递和返回值都是键值对,要将参数传递给任务,在创建WorkRequest
对象之前调用WorkRequest.Builder.setInputData()方法,该方法使用 Data.Builder创建的Data对象。 Worker
类可以通过调用Worker.getInputData()来访问这些参数。要输出一个返回值,任务可以调用带有 Data对象的Worker.setOutputData(),我们可以通过观察任务的LiveData<WorkStatus>
来获取输出。
评论