一、概述
在后台执行任务这块,官方一直再推更好的解决方法,因为的App不可能永远处于前台,但你仍然需要确保那些重要的后台任务将被执行,例如下载,更新,与服务器同步数据等等, google也为我们提供了如下API来处理后台任务,如:AsyncTask JobScheduler AlarmManager Loader Service SyncAdapter IntentService(已废弃) 等等,在我们查看IntentService源码的时候,官方推荐在Android8.0(API level 26)以上,推荐使用WorkManager。
二、多线程任务的选择
在我们执行长时间的后台任务时,如果这些API使用不当,会减少电池电量的使用时长,所以要根据任务的实际场景合理选择哪一种多线程任务方式?如下是官方选择方式:
关键字:
Long-running HTTP downloads : 长时间http下载任务,使用DownloadManager去完成
参考笔者之前写的博客文章:Android DownloadManager 使用_broadview_java的博客-CSDN博客
Deferrable work : 可延期任务, 如果不能延迟,需要立马执行,就开启一个前台服务去完成。
Triggered by system conditions: 由系统条件触发 如果需要精准运行任务,使用AlarmManager
其他就使用 WorkManager 。
三、WorkManager介绍
2.1 WorkManager是什么?
WorkManager属于Android Jetpack的一部分,WorkManager可以在后台执行一些耗时性或周期性的独立的任务,如果工作始终需要通过应用重启或系统重新启动来调度,便是持久性的工作。由于大多数后台处理操作都是通过持久性工作完成的,所以WorkManager是最适合不过了。
android存在如果当你的app退到后台长时间不使用,系统会kill掉该应用进程的机制,假如在你的app启动后台下载任务,不小心应用进程被kill掉了,那么下载请求肯定会失败。再者,假如你正在后台下载任务,突然设备没电关机了,如果你是使用AsyncTask JobScheduler Service来实现的话,关机后一切都over了,即使你充电后重新开机,也需要重新再去执行一遍下载任务。
但是WorkManager不用担心这些,即使应用退出 或者 设备关机重启,通过WorkManager管理的后台任务也不会丢失,待预制条件恢复时,它也能再正常执行,而且WorkManager 遵循低电耗模式等省电功能和最佳做法,因此您在这方面无需担心,综上对比,这也是官方推荐开发者使用WorkManager的理由。
2.2 WorkManager优点
相比其他处理后台任务的 DownloadManager JobScheduler Service 等 我们选择WorkManager的理由有哪些呢?
1. 最高可向后兼容到 API 14,根据如下标准去调度执行任务
在 API 23 及以上级别的设备上运行 使用 JobScheduler实现
在API 14-22的设备上运行 结合使用BroadcastReceiver 和 AlarmManager实现
2. 支持网络可用性或充电状态等工作约束
3. 支持调度一次性任务和周期行的任务
4. 支持监控和管理计划任务
5. 将任务链接起来
6. 确保任务执行,即使应用退出或者设备重启也会执行任务
7. 遵循低电耗模式等省电功能
四、使用 WorkManager 调度任务
WorkManager 可处理三种类型的持久性工作:
- 立即执行:必须立即开始且很快就完成的任务,可以加急。
- 长时间运行:运行时间可能较长(有可能超过 10 分钟)的任务。
- 可延期执行:延期开始并且可以定期运行的预定任务。
大致表明了不同类型的持久性工作彼此之间的关系:
同样,下表大致列出了各种工作类型:
类型 | 周期 | 使用方式 |
立即 | 一次性 | OneTimeWorkRequest 和 Worker 。 如需处理加急工作,请对 OneTimeWorkRequest 调用 |
长期运行 | 一次性或定期 | 任意 WorkRequest 或 Worker 。在工作器中调用 setForeground() 来处理通知。 |
可延期 | 一次性或定期 | PeriodicWorkRequest和Worker |
五、WorkManager 使用入门
5.1 准备工作
如需开始使用 WorkManager,请先将库导入您的 Android 项目中。
将以下依赖项添加到应用的 build.gradle
文件中:
dependencies {
def work_version = "2.7.1"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"
// optional - GCMNetworkManager support
implementation "androidx.work:work-gcm:$work_version"
// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
// optional - Multiprocess support
implementation "androidx.work:work-multiprocess:$work_version"
}
5.2 定义工作
工作使用 Worker 类定义。doWork() 方法在 WorkManager 提供的后台线程上异步运行。如需为 WorkManager 创建一些要运行的工作,请继承 Worker
类并实现 doWork()
方法。例如
如需创建上传图像的 Worker
,您可以执行以下操作:
public class UploadWorker extends Worker {
public UploadWorker(
@NonNull Context context,
@NonNull WorkerParameters params) {
super(context, params);
}
@Override
public Result doWork() {
// Do the work here--in this case, upload the images.
uploadImages();
// Indicate whether the work finished successfully with the Result
return Result.success();
}
}
从 doWork()
返回的RWorkManager 服务工作是否成功,以及工作失败时是否应重试工作。
Result.success()
:工作成功完成。Result.failure()
:工作失败。Result.retry()
:工作失败,应根据其重试策略在其他时间尝试。
5.3 创建 WorkRequest
定义工作后,必须使用 WorkManager 服务进行调度该工作才能运行。对于如何调度工作,WorkManager 提供了很大的灵活性。您可以将其安排为在某段时间内定期运行,也可以将其安排为仅运行一次。
不论您选择以何种方式调度工作,请始终使用 WorkRequest。Worker 定义工作单元,WorkRequest(及其子类)则定义工作运行方式和时间。在最简单的情况下,您可以使用 OneTimeWorkRequest,如以下示例所示。
WorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(UploadWorker.class)
.build();
5.4 将 WorkRequest 提交给系统
最后,您需要使用 enqueue() 方法将 WorkRequest 提交到 WorkManager。
WorkManager
.getInstance(myContext)
.enqueue(uploadWorkRequest);
执行工作器的确切时间取决于 WorkRequest
中使用的约束和系统优化方式。WorkManager 经过设计,能够在满足这些约束的情况下提供最佳行为。
六、定义工作请求
在本指南中,您将了解如何定义和自定义 WorkRequest
对象来处理常见用例,例如:
- 调度一次性工作和重复性工作
- 设置工作约束条件,例如要求连接到 Wi-Fi 网络或正在充电
- 确保至少延迟一定时间再执行工作
- 设置重试和退避策略
- 将输入数据传递给工作
- 使用标记将相关工作分组在一起
6.1 概览
工作通过 WorkRequest 在 WorkManager 中进行定义。为了使用 WorkManager 调度任何工作,您必须先创建一个 WorkRequest 对象,然后将其加入队列。
WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);
WorkRequest 对象包含 WorkManager 调度和运行工作所需的所有信息。其中包括运行工作必须满足的约束、调度信息(例如延迟或重复间隔)、重试配置,并且可能包含输入数据(如果工作需要)。
WorkRequest 本身是抽象基类。该类有两个派生实现,可用于创建 OneTimeWorkRequest 和 PeriodicWorkRequest 请求。顾名思义,OneTimeWorkRequest 适用于调度非重复性工作,而 PeriodicWorkRequest 则更适合调度以一定间隔重复执行的工作。
6.2 调度一次性工作
对于无需额外配置的简单工作,请使用静态方法 from:
WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);
对于更复杂的工作,可以使用构建器:
WorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
// Additional configuration
.build();
6.3 调度加急工作
WorkManager 2.7.0 引入了加急工作的概念。这使 WorkManager 能够执行重要工作,同时使系统能够更好地控制对资源的访问权限。
加急工作具有以下特征:
- 重要性:加急工作适用于对用户很重要或由用户启动的任务。
- 速度:加急工作最适合那些立即启动并在几分钟内完成的简短任务。
- 配额:限制前台执行时间的系统级配额决定了加急作业是否可以启动。
- 电源管理:电源管理限制(如省电模式和低电耗模式)不太可能影响加急工作。
- 延迟时间:系统立即执行加急工作,前提是系统的当前工作负载允许执行此操作。这意味着这些工作对延迟时间较为敏感,不能安排到以后执行。
在用户想要发送消息或附加的图片时,可能会在聊天应用内使用加急工作。同样,处理付款或订阅流程的应用也可能需要使用加急工作。这是因为这些任务对用户很重要,会在后台快速执行,并需要立即开始执行。
配额
系统必须先为加急作业分配应用执行时间,然后才能运行作业。执行时间并非无限制,而是受配额限制。如果您的应用使用其执行时间并达到分配的配额,在配额刷新之前,您无法再执行加急工作。这样,Android 可以更有效地在应用之间平衡资源。
每个应用均有自己的前台执行时间配额。可用的执行时间取决于待机模式存储分区和进程的重要性。
您可以确定在执行配额不允许立即运行加急作业时会出现什么情况。如需了解详情,请参阅以下代码段。
注意:当您的应用在前台运行时,配额不会限制加急工作的执行。仅在应用在后台运行时或当应用移至后台时,执行时间配额才适用。因此,您应在后台加快要继续的工作。当应用在前台运行时,您可以继续使用 setForeground()
。
6.4 执行加急工作
从 WorkManager 2.7 开始,您的应用可以调用 setExpedited()
来声明 WorkRequest
应该使用加急作业,以尽可能快的速度运行。以下代码段展示了关于如何使用 setExpedited()
的示例:
OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<T>()
.setInputData(inputData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build();
在此示例中,我们初始化 OneTimeWorkRequest
的实例并对其调用 setExpedited()
。然后,此请求就会变成加急工作。如果配额允许,它将立即开始在后台运行。
向后兼容性和前台服务
为了保持加急作业的向后兼容性,WorkManager 可能会在 Android 12 之前版本的平台上运行前台服务。前台服务可以向用户显示通知。
在 Android 12 之前,工作器中的 getForegroundInfoAsync()
和 getForegroundInfo()
方法可让 WorkManager 在您调用 setExpedited()
时显示通知。
如果您想要请求任务作为加急作业运行,则所有的 ListenableWorker 都必须实现 getForegroundInfo
方法。
注意:如果未能实现对应的 getForegroundInfo
方法,那么在旧版平台上调用 setExpedited
时,可能会导致运行时崩溃。
以 Android 12 或更高版本为目标平台时,前台服务仍然可通过对应的 setForeground
方法使用。
注意:setForeground()
可能会在 Android 12 上抛出运行时异常,并且在启动受到限制时可能会抛出异常。
6.5 调度定期工作
您的应用有时可能需要定期运行某些工作。例如,您可能要定期备份数据、定期下载应用中的新鲜内容或者定期上传日志到服务器。
使用 PeriodicWorkRequest 创建定期执行的 WorkRequest 对象的方法如下:
PeriodicWorkRequest saveRequest =
new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
// Constraints
.build();
在此示例中,工作的运行时间间隔定为一小时。
时间间隔定义为两次重复执行之间的最短时间。工作器的确切执行时间取决于您在 WorkRequest 对象中设置的约束以及系统执行的优化。
注意:可以定义的最短重复间隔是 15 分钟(与 JobScheduler API 相同)。
6.6 工作约束
约束可确保将工作延迟到满足最佳条件时运行。以下约束适用于 WorkManager。
如需创建一组约束并将其与某项工作相关联,请使用一个 Contraints.Builder()
创建 Constraints
实例,并将该实例分配给 WorkRequest.Builder()
。
例如,以下代码会构建了一个工作请求,该工作请求仅在用户设备正在充电且连接到 Wi-Fi 网络时才会运行:
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build();
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setConstraints(constraints)
.build();
如果指定了多个约束,工作将仅在满足所有约束时才会运行。
如果在工作运行时不再满足某个约束,WorkManager 将停止工作器。系统将在满足所有约束后重试工作。
6.7 延迟工作
如果工作没有约束,或者当工作加入队列时所有约束都得到了满足,那么系统可能会选择立即运行该工作。如果您不希望工作立即运行,可以将工作指定为在经过一段最短初始延迟时间后再启动。
下面举例说明了如何将工作设置为在加入队列后至少经过 10 分钟后再运行。
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setInitialDelay(10, TimeUnit.MINUTES)
.build();
该示例说明了如何为 OneTimeWorkRequest
设置初始延迟时间,您也可以为 PeriodicWorkRequest
设置初始延迟时间。在这种情况下,定期工作只有首次运行时会延迟。
注意:执行工作器的确切时间还取决于 WorkRequest 中使用的约束和系统优化方式。WorkManager 经过设计,能够在满足这些约束的情况下提供可能的最佳行为。
6.8 重试和退避政策
如果您需要让 WorkManager 重试工作,可以从工作器返回 Result.retry()。然后,系统将根据退避延迟时间和退避政策重新调度工作。
退避延迟时间指定了首次尝试后重试工作前的最短等待时间。此值不能超过 10 秒(或 MIN_BACKOFF_MILLIS)。
退避政策定义了在后续重试过程中,退避延迟时间随时间以怎样的方式增长。WorkManager 支持 2 个退避政策,即 LINEAR 和 EXPONENTIAL。
每个工作请求都有退避政策和退避延迟时间。默认政策是 EXPONENTIAL,延迟时间为 10 秒,但您可以在工作请求配置中替换此设置。
以下是自定义退避延迟时间和政策的示例。
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build();
在本示例中,最短退避延迟时间设置为允许的最小值,即 10 秒。由于政策为 LINEAR
,每次尝试重试时,重试间隔都会增加约 10 秒。例如,第一次运行以 Result.retry()
结束并在 10 秒后重试;然后,如果工作在后续尝试后继续返回 Result.retry()
,那么接下来会在 20 秒、30 秒、40 秒后重试,以此类推。如果退避政策设置为 EXPONENTIAL
,那么重试时长序列将接近 20、40、80 秒,以此类推。
注意:退避延迟时间不精确,在两次重试之间可能会有几秒钟的差异,但绝不会低于配置中指定的初始退避延迟时间。
6.9 标记工作
每个工作请求都有一个唯一标识符,该标识符可用于在以后标识该工作,以便取消工作或观察其进度。
如果有一组在逻辑上相关的工作,对这些工作项进行标记可能也会很有帮助。通过标记,您一起处理一组工作请求。
例如,WorkManager.cancelAllWorkByTag(String) 会取消带有特定标记的所有工作请求,WorkManager.getWorkInfosByTag(String) 会返回一个 WorkInfo 对象列表,该列表可用于确定当前工作状态。
以下代码展示了如何向工作添加“cleanup”标记:
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.addTag("cleanup")
.build();
最后,可以向单个工作请求添加多个标记。这些标记在内部以一组字符串的形式进行存储。您可以使用 WorkInfo.getTags() 获取与 WorkRequest 关联的标记集。
从 Worker 类中,您可以通过 ListenableWorker.getTags() 检索其标记集。
6.10 分配输入数据
您的工作可能需要输入数据才能正常运行。例如,处理图片上传的工作可能需要使用待上传图片的 URI 作为输入数据。
输入值以键值对的形式存储在 Data 对象中,并且可以在工作请求中设置。WorkManager 会在执行工作时将输入 Data 传递给工作。Worker 类可通过调用 Worker.getInputData() 访问输入参数。以下代码展示了如何创建需要输入数据的 Worker 实例,以及如何在工作请求中发送该实例。
// Define the Worker requiring input
public class UploadWork extends Worker {
public UploadWork(Context appContext, WorkerParameters workerParams) {
super(appContext, workerParams);
}
@NonNull
@Override
public Result doWork() {
String imageUriInput = getInputData().getString("IMAGE_URI");
if(imageUriInput == null) {
return Result.failure();
}
uploadFile(imageUriInput);
return Result.success();
}
...
}
// Create a WorkRequest for your Worker and sending it input
WorkRequest myUploadWork =
new OneTimeWorkRequest.Builder(UploadWork.class)
.setInputData(
new Data.Builder()
.putString("IMAGE_URI", "http://...")
.build()
)
.build();
同样,可使用 Data
类输出返回值。如需详细了解输入和输出数据,请参阅输入参数和返回值部分。
七、参考文章
1. 官方文档:https://developer.android.google.cn/topic/libraries/architecture/workmanager
2. WorkManger介绍视频:Android Jetpack WorkManager | Android 中文教学视频_哔哩哔哩_bilibili