一、WorkManager 后台任务:任务链、约束条件与生命周期管理

大家好,我是你们的老朋友。今天我们来聊聊 WorkManager——这个 Android 官方钦定的后台任务解决方案。

说实话,我刚接触 Android 后台任务时,踩过不少坑。Service 被系统杀、JobScheduler 兼容性问题、闹钟管理器不准……直到 WorkManager 出现,我才觉得「嗯,这才是该有的样子」。

WorkManager 的核心价值是什么?说白了,就是帮你把后台任务管得服服帖帖——哪怕 App 被杀了、手机重启了,任务照样能跑。它不是一个简单的线程池,而是一个任务调度框架。

1.1 一次性任务与周期性任务

WorkManager 把任务分成两大类:一次性任务周期性任务。这个区分很直观,但细节里藏着不少门道。

一次性任务(OneTimeWorkRequest)

一次性任务,顾名思义,只执行一次。我习惯用它来做「数据同步」、「日志上传」这类操作。

val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueue(uploadWork)

这里有个小细节:enqueue 不是立即执行,而是把任务加入队列。WorkManager 会根据约束条件和系统资源来决定何时运行。

周期性任务(PeriodicWorkRequest)

周期性任务适合「定期清理缓存」、「定时上报位置」这类场景。但要注意——它不保证精确的时间间隔。

val periodicWork = PeriodicWorkRequestBuilder<CleanupWorker>(
        15, TimeUnit.MINUTES
    )
    .setConstraints(
        Constraints.Builder()
            .setRequiresBatteryNotLow(true)
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "cleanup",
    ExistingPeriodicWorkPolicy.KEEP,
    periodicWork
)
⚠️ 重要提醒: 周期性任务的最小间隔是 15 分钟。你设成 5 分钟?系统会直接忽略,按 15 分钟处理。我曾经在这个坑里爬了半天……

1.2 任务链:让任务按顺序执行

实际项目中,任务往往有依赖关系。比如:先下载配置文件,再初始化 SDK,最后上报状态。这时候任务链就派上用场了。

WorkManager 的任务链支持两种模式:顺序执行并行执行

顺序任务链

val download = OneTimeWorkRequestBuilder<DownloadWorker>().build()
val init = OneTimeWorkRequestBuilder<InitWorker>().build()
val report = OneTimeWorkRequestBuilder<ReportWorker>().build()

WorkManager.getInstance(context)
    .beginWith(download)
    .then(init)
    .then(report)
    .enqueue()

这段代码的意思是:download 执行完 → init 执行 → report 执行。任何一个环节失败,后续任务都会被取消。

💡 个人经验: 我在做电商 App 时,用任务链实现了「商品图片下载 → 压缩 → 上传」的流水线。每个环节独立成 Worker,出了问题可以单独重试,维护起来特别清爽。

并行任务链

有时候多个任务没有依赖关系,可以同时跑。比如同时下载图片和视频。

val imageDownload = OneTimeWorkRequestBuilder<ImageWorker>().build()
val videoDownload = OneTimeWorkRequestBuilder<VideoWorker>().build()

WorkManager.getInstance(context)
    .beginWith(listOf(imageDownload, videoDownload))
    .then(mergeWork)
    .enqueue()

beginWith 可以传入一个 List,表示这些任务并行执行。等它们全部完成后,再执行 then 里的任务。

1.3 约束条件:让任务在合适的时机运行

约束条件是 WorkManager 最强大的特性之一。你可以告诉系统:「这个任务必须在 WiFi 下、电量充足、设备空闲时才能执行」。

约束类型 说明 使用场景
NetworkType 网络类型要求 大文件下载需要 WiFi
RequiresBatteryNotLow 电量不能低 避免低电量时耗电
RequiresCharging 需要充电 数据备份等耗电操作
RequiresDeviceIdle 设备空闲 后台清理、索引构建
RequiresStorageNotLow 存储空间充足 下载大文件
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.UNMETERED)  // 仅 WiFi
    .setRequiresCharging(true)                      // 充电中
    .setRequiresDeviceIdle(true)                    // 设备空闲
    .build()

val backupWork = OneTimeWorkRequestBuilder<BackupWorker>()
    .setConstraints(constraints)
    .build()
🎯 核心思路: 约束条件不是限制,而是保护。保护用户体验,保护电池寿命,保护流量。我见过不少开发者滥用后台任务,结果被用户投诉「App 太耗电」——约束条件就是你的护身符。

1.4 Worker 的输入、输出与进度更新

Worker 不是孤立的。它需要接收数据,返回结果,甚至实时报告进度。

输入数据(InputData)

val inputData = workDataOf(
    "url" to "https://example.com/file.zip",
    "retry_count" to 3
)

val downloadWork = OneTimeWorkRequestBuilder<DownloadWorker>()
    .setInputData(inputData)
    .build()

在 Worker 中获取输入:

class DownloadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        val url = inputData.getString("url") ?: return Result.failure()
        val retryCount = inputData.getInt("retry_count", 0)
        // 执行下载逻辑
        return Result.success()
    }
}

输出数据(OutputData)

Worker 执行完后,可以返回结果给下一个任务:

class DownloadWorker : Worker() {
    override fun doWork(): Result {
        val filePath = downloadFile()
        val output = workDataOf("file_path" to filePath)
        return Result.success(output)
    }
}

// 下一个 Worker 获取输出
class ProcessWorker : Worker() {
    override fun doWork(): Result {
        val filePath = inputData.getString("file_path") ?: return Result.failure()
        // 处理文件
        return Result.success()
    }
}

进度更新

这个功能我特别喜欢。你可以实时告诉 UI 层「任务进行到哪了」。

class UploadWorker : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        val total = 100
        for (i in 1..total) {
            // 模拟上传进度
            delay(100)
            setProgress(workDataOf("progress" to i))
        }
        return Result.success()
    }
}

// 在 Activity/Fragment 中观察进度
WorkManager.getInstance(context)
    .getWorkInfoByIdLiveData(workRequest.id)
    .observe(this) { workInfo ->
        val progress = workInfo.progress.getInt("progress", 0)
        progressBar.progress = progress
    }
💡 避坑指南: 我曾经在进度更新里传了一个大对象,结果导致序列化异常。记住,setProgress 的数据也要通过 workDataOf 传递,只支持基本类型和 String。

1.5 WorkManager 与 ForegroundService 的对比

很多新手会问:「有了 WorkManager,是不是就不用 ForegroundService 了?」

答案是否定的。它们各有各的战场。

对比维度 WorkManager ForegroundService
生命周期 由系统调度,可延迟、可重试 由开发者控制,必须显示通知
系统限制 受 Doze 模式影响 Android 12+ 有前台服务启动限制
适用场景 可延迟的后台任务(同步、备份) 用户可感知的持续任务(音乐播放、位置追踪)
电量优化 系统自动优化 需要开发者自行管理
任务持久化 支持(重启后恢复) 不支持

我的建议是:能用 WorkManager 就别用 ForegroundService。但如果你需要做「音乐播放器」或者「实时导航」,那 ForegroundService 还是绕不开的。

⚠️ 注意: Android 12 开始,ForegroundService 的启动限制越来越严格。如果你还在用 Service 做后台下载,赶紧迁移到 WorkManager 吧——别等到 Google Play 审核不通过才后悔。

1.6 知识体系总览

下面这张图,是我梳理的 WorkManager 核心知识结构。你可以把它当作学习地图:

WorkManager 任务类型 一次性任务 周期性任务 任务链 顺序执行 并行执行 约束条件 网络/电量 空闲/存储 Worker 机制 输入/输出 进度更新 对比 ForegroundService 生命周期 适用场景 核心:延迟执行 + 约束条件 + 任务链 + 持久化

这张图把 WorkManager 的核心模块串起来了。你从「任务类型」入手,然后理解「任务链」和「约束条件」,再深入「Worker 机制」,最后对比「ForegroundService」——整个知识体系就清晰了。

好了,这一章的内容就到这里。记住,WorkManager 不是万能的,但在 90% 的后台任务场景下,它都是最优解。下一章我们会深入源码,看看 WorkManager 内部是怎么调度任务的——到时候你会发现,它的设计思路真的很巧妙。


公众号:蓝海资料掘金营,微信deep3321