1.概览
官方文档:WorkManager
谷歌实验室:官方教程
官方案例:android-workmanager
WorkManger介绍视频:中文官方介绍视频
谷歌工程师博客:https://medium.com/androiddevelopers/workmanager-basics-beba51e94048
Android JetPack实例学习:https://www.jianshu.com/p/68e720b8a939
1.1 定义
使用 WorkManager API 可以轻松地调度即使在应用退出或设备重启时仍应运行的可延迟异步任务。
WorkManager, a compatible, flexible and simple library for deferrable background work.
WorkManger是一个可兼容、灵活和简单的延迟后台任务。
1.2 主要功能
最高向后兼容到 API 14
- 在运行 API 23 及以上级别的设备上使用 JobScheduler
- 在运行 API 14-22 的设备上结合使用 BroadcastReceiver 和 AlarmManager
- 添加网络可用性或充电状态等工作约束
- 调度一次性或周期性异步任务
- 监控和管理计划任务
- 将任务链接起来
- 确保任务执行,即使应用或设备重启也同样执行任务
- 遵循低电耗模式等省电功能
WorkManager 旨在用于可延迟运行(即不需要立即运行)并且在应用退出或设备重启时必须能够可靠运行的任务。例如:
- 向后端服务发送日志或分析数据
- 定期将应用数据与服务器同步
WorkManager 不适用于应用进程结束时能够安全终止的运行中后台工作,也不适用于需要立即执行的任务。请查看后台处理指南,了解哪种解决方案符合您的需求。
2. 使用入门
2.1 添加依赖
dependencies { def work_version = "2.3.4" // (Java only) implementation "androidx.work:work-runtime:$work_version" // Kotlin + coroutines implementation "androidx.work:work-runtime-ktx:$work_version" }
2.2 方法指南
WorkManager 中几个重要类
Worker | 创建后台任务,定义工作单元。继承Worker,复写doWork() ,实现需要执行代码。 |
WorkRequest | 定义工作得运行方式和时间,以及任务在运行时应遵循得约束。 |
WorkManager | 将任务 (WorkRequest) 提交给系统执行。 |
- WorkRequet创建完成后,InterTaskExecutor立即将它保存到WorkManager数据库中。
- 当满足WorkRequest的约束条件(Constaraints) 时(可以立即),Internal TaskExecutorr告诉WorkerFactory创建一个Worker。
- 从主线程调用worker的doWork()方法
2.2.1 Worker
- 无变量(清理缓存)
class CleanUpWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { // ... 省略 return try { // 删除逻辑 // ...代码省略 // 成功时返回 Result.success() } catch (exception: Exception) { // 失败时返回 Result.failure() } }
}
- 任务的输入输出(需要同worker进行数据交互)
注意:下面实例中,使用Data参数传递,Data 对象应该很小,值可以是字符串、基元类型或数组变体。Data 对象的大小上限为10kb。如果需要将更多数据传入和传出Worker,应该将数据放在其他位置,例如 Room 数据库
返回结果,会通知WorkManager任务:
- 已成功完成:Result.success()
- 已失败:Result.failure()
- 需要稍后重试:Result.retry()
传入本地图片地址,将图片上传,并返回url地址:
class UploadWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { override fun doWork(): Result { try { // Get the input 获取传入图片地址 ***** val imageUriInput = inputData.getString(Constants.KEY_IMAGE_URI) // Do the work 图片上传 val response = upload(imageUriInput) // Create the output of the work val imageResponse = response.body() val imgLink = imageResponse.data.link // workDataOf (part of KTX) converts a list of pairs to a [Data] object. // 返回图片url ***** val outputData = workDataOf(Constants.KEY_IMAGE_URI to imgLink) return Result.success(outputData) } catch (e: Exception) { return Result.failure() } } fun upload(imageUri: String): Response { TODO(“Webservice request code here”) // Webservice request code here; note this would need to be run // synchronously for reasons explained below. }
}
2.2.2 WorkRequest
//创建周期任务,定时每15天,清理缓存
var clearRequest = PeriodicWorkRequestBuilder(15, TimeUnit.DAYS).build()
//创建约束条件
val constraints = Constraints.Builder() .setRequiresBatteryNotLow(true) //设备电量处于充足 .setRequiredNetworkType(NetworkType.CONNECTED) //网络状态约束(网络连接正常) .setRequiresCharging(true) //设备充电时才能执行 .setRequiresStorageNotLow(true) //设备储存充足 .setTriggerContentMaxDelay(15,TimeUnit.DAYS)//设置从第一次检测到内容更改到计划的时间所允许的最大延迟 .setTriggerContentUpdateDelay(15,TimeUnit.DAYS)//设置从第一次检测到内容更改到计划的时间所允许的延迟 .build() // 创建单次任务,图片上传获取url,传入本地图片地址
// define the input
val imageData = workDataOf(Constants.KEY_IMAGE_URI to imageUriString)
val uploadWorkRequest = OneTimeWorkRequestBuilder() .setInputData(imageData) //定义任务的输入输出 .setInitialDelay(10,TimeUnit.MINUTES) //初始延迟 .setConstraints(constraints) //添加约束条件 .setBackoffCriteria( //自定义退避延迟时间和政策 BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .addTag("upload") //标记工作 .build()
- 创建任务类型
PeriodicWorkRequest | 多次、周期性任务请求,不支持任务链 |
OneTimeWorkRequest | 只执行一次的任务请求,支持任务链 |
- 添加任务约束
- 初始延迟
- 定义任务的输入输出
轻量级数据可使用Data进行传递
val imageData = workDataOf(KEY_IMAGE_URI to imageUriString)
val imageData = Data.Builder().putString(KEY_IMAGE_URI, imageUriString).build()
- 重试和退避政策
当worker执行结果为Result.retry(),系统会根据默认的退避延迟时间和政策重新调度您的工作。退避延迟时间指定了重试工作前的最短等待时间。退避政策定义了在后续重试过程中,退避延迟时间随时间以怎样的方式增长;默认情况下按 EXPONENTIAL
延长可以通过BackoffCriteria来自定义退避延迟时间和政策。
- 标记工作
通过为 WorkRequest 分配标记字符串,按逻辑对任务进行分组。这样就可以对使用特定标记的所有任务执行操作。
例如,
WorkManager.cancelAllWorkByTag(String) :取消使用特定标记的所有任务
WorkManager.getWorkInfosByTagLiveData(String) :返回 LiveData 和具有该标记的所有任务的状态列表。
2.2.3 WorkManager
- 单任务执行
workManager.enqueue(clearRequest )
- 链接工作
多任务时,使用WorkManager创建工作链并为其排队。工作链指定多个关联任务并定义任务的执行顺序。
//当filter1三个全部执行完成之后,执行compress,然后upload
WorkManager.getInstance(myContext) // Candidates to run in parallel // 多任务时并行运行 .beginWith(listOf(filter1, filter2, filter3)) // Dependent work (only runs after all previous work in chain) //从属任务(仅在链中所有先前工作之后运行) .then(compress) .then(upload) // Don't forget to enqueue() .enqueue()
WorkManager.getInstance() .beginUniqueWork( uniqueWorkName, //唯一工作序列名称 ExistingWorkPolicy, //冲突解决策略 requestA) .then(requestB) .then(requestC) .enqueue(); /** * enqueueUniqueWork:通过此方法设定一次只能激活一个特定名称的后台任务 * singleWork:进行此操作的唯一名称,之后状态监听要获取此名称 * ExistingWorkPolicy:工作策略(REPLACE:取消现有的序列并用新序列替换 KEEP:保持现有顺序并忽略新的请求 APPEND:将新序列附加到现有学列,在现有序列的最后一个任务完成后运行新序列的第一个任务) * request:自行构建的一次性任务或者周期性任务 * 如何监听此任务状态,上述有代码 */ WorkManager.getInstance().enqueueUniqueWork("uniqueWorkName", ExistingWorkPolicy, request);
beginWith() | 开启链式任务,任务允许重复。 |
beginUniqueWork() | 开启链式任务,任务禁止重复。如需创建一个唯一工作链时使用 |
注意:WorkerManager要求必须在前一个后台任务运行成功后,下一个后台任务才会运行,也就是说,如果某个后台任务运行失败,或者被取消了,那么接下来的后台任务就都得不到运行了。
uniqueWorkName:唯一工作链名称,在状态监听时
ExistingWorkPolicy:它指定了如果已经存在一个具有该唯一名称的未完成工作链时,如何解决冲突
REPLACE | 取消现有工作链,并将其 REPLACE 为新工作链。 |
KEEP | KEEP 现有序列并忽略您的新请求。 |
APPEND | 将新序列 APPEND 到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务。您不能将 APPEND 与 PeriodicWorkRequest 一起使用。 |
2.3 工作状态和观差工作
2.3.1工作状态
工作生命周期,不同的State标识。观察LiveData
BLOCKED | 如果有尚未完成的前提性工作 |
ENQUEUED | 如果work能够能够在满足Constrains和时机条件后立即运行 |
RUNNING | 当work处于活跃执行时 |
SUCCESSED | 返回Result.success(),终止State,只有OneTimeWorkRequest可以进入这种State 返回Result.fail(),终止State,所有依赖工作也会被标记,并且不会运行,只有OneTimeWorkRequest可以进入这种State。 |
CANCELLED | 取消尚未终止的WorkRequest,进入此状态,所有依赖工作也会被标记,且不会运行 |
2.3.2 观差工作
将工作加入队列后,您可以通过 WorkManager 检查其状态。相关信息在 WorkInfo 对象中提供,包括工作的 id、标签、当前 State 和任何输出数据。
获取WorkInfo方式:
Request Id | 对于特定的 WorkRequest,您可以利用 WorkManager.getWorkInfoById(UUID) 或 WorkManager.getWorkInfoByIdLiveData(UUID) 来通过 WorkRequest id 检索其 WorkInfo。 |
Request Tag | 对于指定的标记,您可以利用 WorkManager.getWorkInfosByTag(String) 或 WorkManager.getWorkInfosByTagLiveData(String) 检索所有匹配的 WorkRequest 的 WorkInfo 对象。 |
UniqueName(唯一工作名称) | 对于唯一工作名称,您可以利用 WorkManager.getWorkInfosForUniqueWork(String) 或 WorkManager.getWorkInfosForUniqueWorkLiveData(String) 检索所有匹配的 WorkRequest 的 WorkInfo 对象。 |
// In your UI (activity, fragment, etc)
//通过request id 监听单个任务执行状态
WorkManager.getInstance().getWorkInfoByIdLiveData(uploadWorkRequest.id) .observe(lifecycleOwner, Observer { workInfo -> // Check if the current work's state is "successfully finished" if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) { displayImage(workInfo.outputData.getString(KEY_IMAGE_URI)) } })
//通过Tag标签监听同一标签下所有后台任务请求的运行结果
WorkManager.getInstance().getWorkInfosByTagLiveData("upload") .observe(lifecycleOwner, Observer { workInfo-> //遍历所有tag标签的workInfo for (WorkInfo workInfo : workInfos) { if (workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED) { displayImage(workInfo.outputData.getString(KEY_IMAGE_URI)) } } })
// 如果是通过enqueueUniqueWork(“workName")执行
WorkManager.getInstance().getWorkInfosForUniqueWorkLiveData("workName") .observe(lifecycleOwner, observer { workInfo-> for (WorkInfo workInfo : workInfos) { if (workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED) { displayImage(workInfo.outputData.getString(KEY_IMAGE_URI)) } } })
注意:
- 每个WorkRequset都有一个唯一id (由WorkManager自动生成),并且该唯一ID是查找关联的WorkInfo的一种方法。
- 利用每个方法的LiveData变量,您可以通过注册监听器来观察
WorkInfo
的变化。例如上面实例,想要在图片上传成功后向用户显示上传后得到的url。