Android WorkManager 后台任务设计
Android WorkManager 后台任务设计
上传日志、同步离线数据、清理缓存等任务往往不要求立即完成,但需要在应用离开前台甚至进程重启后继续执行。此类工作如果只放在普通协程或线程中,进程被回收后就会丢失。WorkManager 适合管理可延迟、需要保证最终执行的后台任务,但并不适合所有异步需求。
一、先判断任务是否适合 WorkManager
适合的任务通常具备以下特征:
- 可以延迟到系统允许的时间执行。
- 进程重启后仍需要保留。
- 能够声明网络、电量等运行条件。
- 失败后允许按策略重试。
页面内的数据加载、即时动画、短时间倒计时不应交给 WorkManager。要求用户立刻感知的长任务,则需要结合项目已有的前台执行方案。
二、设计可重复执行的 Worker
系统可能因进程终止、约束变化或重试策略再次执行同一任务。因此 Worker 应尽可能具备幂等性。
下面的示例假设项目已经通过统一的 WorkerFactory 提供仓库依赖:
class DraftUploadWorker(
appContext: Context,
params: WorkerParameters,
private val repository: DraftRepository
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
val draftId = inputData.getString(KEY_DRAFT_ID)
?: return Result.failure()
return when (repository.uploadDraft(draftId)) {
UploadResult.Success -> Result.success()
UploadResult.RetryableFailure -> Result.retry()
UploadResult.PermanentFailure -> Result.failure()
}
}
companion object {
const val KEY_DRAFT_ID = "draft_id"
}
}
幂等可以通过稳定任务标识、服务端去重标识或本地状态机实现。不要假设 doWork 只会调用一次。
三、明确成功、失败和重试
三种结果表达不同含义:
success:本次工作已经达到目标,不再重试。failure:输入无效或业务明确拒绝,重复执行也不会改变结果。retry:网络暂时不可用、服务暂时繁忙等情况,之后可能成功。
所有异常都返回重试会制造永不结束的任务。参数缺失、权限被永久拒绝、业务对象已经删除等情况应直接失败或按业务视为完成。
取消也不等于失败。CoroutineWorker 被停止后,应让取消信号正常传播,不要捕获后转换成重试。
四、声明真实运行约束
约束可以避免任务在不合适的环境执行:
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val request = OneTimeWorkRequestBuilder<DraftUploadWorker>()
.setConstraints(constraints)
.setInputData(
workDataOf(DraftUploadWorker.KEY_DRAFT_ID to draftId)
)
.build()
约束越严格,任务可能等待越久。只有确实需要充电、空闲或特定网络类型时才声明,不要把它们当作通用省电开关。
五、使用唯一任务避免重复排队
同一份草稿被连续点击上传时,可以使用唯一工作名称控制重复行为。
workManager.enqueueUniqueWork(
"upload_draft_$draftId",
ExistingWorkPolicy.KEEP,
request
)
KEEP 表示已有未完成任务时保留旧任务。若新参数必须覆盖旧任务,则可根据业务选择替换策略。策略没有绝对优劣,关键是定义同名任务之间的关系。
周期同步也应使用唯一周期任务,避免每次应用启动都增加一条相同计划。
六、控制输入与输出数据
WorkManager 的输入输出适合少量基础类型,不适合传递大型对象或文件内容。大型数据应保存到数据库或文件中,任务只接收稳定标识,再从持久化层读取。
这样还能避免对象结构变化导致反序列化失败,并让任务在进程重启后获得最新数据。
七、组织任务链
有明确依赖的工作可以串联,例如先压缩文件,再上传,最后更新本地状态。
workManager
.beginUniqueWork(
"publish_$draftId",
ExistingWorkPolicy.KEEP,
compressRequest
)
.then(uploadRequest)
.then(markPublishedRequest)
.enqueue()
链条越长,失败恢复越复杂。每个 Worker 应有清晰输入、输出和幂等边界。如果某两步必须在同一数据库事务中完成,就不适合拆成两个独立 Worker。
八、观察任务状态
页面可以观察工作状态并映射为业务界面,但不要把 WorkInfo 直接扩散到所有界面层。仓库或任务管理组件可以把排队、运行、完成和失败转换成稳定业务状态。
用户退出页面后,观察者可以停止,后台任务仍按计划执行。再次进入时,通过稳定任务名或业务记录恢复当前状态。
九、兼容性与系统限制
后台执行策略会随系统版本变化,应依赖项目现有 WorkManager 版本和兼容封装,不直接使用超出最低兼容范围的新能力。系统可能延迟任务,这是节电策略的一部分,业务设计不能依赖精确到秒的执行时间。
十、测试关键路径
建议覆盖:
- 网络不满足时任务是否保持等待。
- 可重试错误是否按退避策略再次执行。
- 永久错误是否停止重试。
- 重复提交是否只保留预期任务。
- 进程重建后输入数据是否仍可恢复。
- Worker 被取消时底层请求是否停止。
总结
WorkManager 解决的是“满足条件后最终执行”的持久后台工作。通过幂等 Worker、准确的结果分类、必要的运行约束和唯一任务策略,可以避免重复上传与无限重试。页面即时任务和精确定时需求则应选择更符合语义的现有组件。
- 点赞
- 收藏
- 关注作者
评论(0)