一、WorkManager 到底是什么?

说实话,刚接触 Android 后台任务时,我踩过不少坑。Service 被系统杀、JobScheduler 兼容性差、AlarmManager 不够灵活……直到 WorkManager 出现,我才觉得「嗯,这才是该有的样子」。

WorkManager 是 Jetpack 家族里专门处理后台任务的组件。它的核心思想很简单:保证任务一定会被执行,哪怕手机重启、应用退出。你想想看,这比你自己写一套任务队列靠谱多了。

核心特性一览:

  • 兼容 API 14+,底层自动适配不同版本
  • 支持一次性任务和周期性任务
  • 可以设置约束条件(比如只在充电时执行)
  • 支持链式任务,一个接一个跑
  • 即使进程被杀,任务也会被持久化

我个人习惯把 WorkManager 理解成一个「智能任务管家」。你只管告诉它做什么、什么时候做,剩下的交给它。

二、Worker 定义——你的任务逻辑

Worker 是 WorkManager 里最基础的单元。说白了,它就是你要执行的代码块。

2.1 如何定义一个 Worker?

继承 Worker 类,重写 doWork() 方法就行。我一般这样写:

class UploadWorker(
    context: Context,
    params: WorkerParameters
) : Worker(context, params) {

    override fun doWork(): Result {
        return try {
            // 你的后台逻辑
            uploadFile()
            Result.success()
        } catch (e: Exception) {
            // 失败后可以重试
            Result.retry()
        }
    }
}

这里有个细节:doWork() 是在后台线程执行的,所以你不用操心线程切换。但要注意,它默认是同步的,如果你在里面开协程,得自己管理生命周期。

我的经验:曾经有个同事在 doWork() 里直接调用了挂起函数,结果任务莫名其妙就结束了。后来才发现,Worker 不会等协程完成。所以要么用 CoroutineWorker,要么自己控制同步。

2.2 Result 的三种状态

状态 含义 适用场景
Result.success() 任务成功完成 正常执行完毕
Result.failure() 任务失败,不再重试 不可恢复的错误
Result.retry() 任务失败,需要重试 网络超时、临时错误

你可能会问:重试策略怎么控制?别急,后面会讲到。

三、WorkRequest——任务的「说明书」

Worker 定义了「做什么」,WorkRequest 定义了「怎么做」。它俩是搭档关系。

3.1 创建 WorkRequest

最常用的两种:

// 一次性任务
val oneTimeRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setInitialDelay(10, TimeUnit.SECONDS)
    .build()

// 周期性任务
val periodicRequest = PeriodicWorkRequestBuilder<UploadWorker>(
        15, TimeUnit.MINUTES
    )
    .build()

注意:周期性任务的最小间隔是 15 分钟。这是系统强制的,你设 5 分钟也没用。我刚开始不知道,调试了半天……

3.2 输入输出数据

WorkManager 支持通过 Data 对象传递参数:

val inputData = workDataOf("file_path" to "/sdcard/photo.jpg")

val request = OneTimeWorkRequestBuilder<UploadWorker>()
    .setInputData(inputData)
    .build()

在 Worker 里取数据:

val filePath = inputData.getString("file_path")

注意:Data 对象有大小限制,不能超过 10KB。如果你需要传大对象,建议存到本地再传路径。

四、约束条件——让任务更智能

约束条件是 WorkManager 最实用的功能之一。你可以指定任务在什么环境下执行。

4.1 常用约束

val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)  // 需要网络
    .setRequiresCharging(true)                      // 需要充电
    .setRequiresDeviceIdle(true)                    // 需要设备空闲
    .setRequiresStorageNotLow(true)                 // 存储空间充足
    .build()

val request = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(constraints)
    .build()

我曾经做过一个日志上传功能,就用了这个组合:有 Wi-Fi + 充电 + 设备空闲。这样既不影响用户体验,又能保证数据上传。

4.2 约束的触发机制

当约束条件不满足时,任务会处于 BLOCKED 状态。一旦条件满足,系统会自动恢复执行。你不需要写任何轮询代码。

五、链式任务——任务编排的艺术

实际项目中,很少有单一任务。比如:先下载图片 → 压缩 → 上传。这时候链式任务就派上用场了。

5.1 基本链式

WorkManager.getInstance(context)
    .beginWith(downloadRequest)
    .then(compressRequest)
    .then(uploadRequest)
    .enqueue()

每个任务会按顺序执行,前一个成功后才启动下一个。如果中间某个任务返回 failure(),整个链会终止。

5.2 并行与合并

有时候需要先并行做两件事,再合并结果:

val parallel1 = OneTimeWorkRequestBuilder<DownloadWorker>().build()
val parallel2 = OneTimeWorkRequestBuilder<DownloadWorker>().build()

WorkManager.getInstance(context)
    .beginWith(listOf(parallel1, parallel2))
    .then(mergeRequest)
    .enqueue()

我的习惯:链式任务里,我会给每个 Worker 加上唯一标签,方便后续观察状态。比如 .addTag("download")

六、知识体系总览

下面这张图是我梳理的 WorkManager 核心逻辑,你看完应该能有个整体印象:

WorkManager 核心知识体系 WorkManager Worker 定义任务逻辑 WorkRequest 配置执行策略 Constraints 约束条件 Worker 类型 Worker / CoroutineWorker Result: success/failure/retry 请求类型 OneTimeWorkRequest PeriodicWorkRequest 约束条件 网络 / 充电 / 空闲 存储空间 / 电池电量 链式任务:beginWith → then → enqueue

七、实战:后台任务调度

光说不练假把式。我们做一个完整的例子:用户上传头像,先压缩再上传。

7.1 定义 Worker

class CompressWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        val filePath = inputData.getString("file_path") ?: return Result.failure()
        // 压缩逻辑
        val compressedPath = compressImage(filePath)
        val output = workDataOf("compressed_path" to compressedPath)
        return Result.success(output)
    }
}

class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        val path = inputData.getString("compressed_path") ?: return Result.failure()
        // 上传逻辑
        return if (uploadToServer(path)) Result.success() else Result.retry()
    }
}

7.2 编排任务

val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>()
    .setInputData(workDataOf("file_path" to "/sdcard/avatar.jpg"))
    .addTag("compress")
    .build()

val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .addTag("upload")
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .build()

WorkManager.getInstance(context)
    .beginWith(compressRequest)
    .then(uploadRequest)
    .enqueue()

7.3 观察状态

WorkManager.getInstance(context)
    .getWorkInfosByTagLiveData("upload")
    .observe(this) { workInfos ->
        workInfos.forEach { info ->
            when (info.state) {
                WorkInfo.State.SUCCEEDED -> showToast("上传成功")
                WorkInfo.State.FAILED -> showToast("上传失败")
                WorkInfo.State.RUNNING -> showProgress(true)
                else -> {}
            }
        }
    }

避坑指南:我曾经在链式任务里忘记传递数据,导致第二个 Worker 拿不到压缩后的路径。记得用 Result.success(output) 把数据传下去。

八、总结

WorkManager 的设计思路其实很清晰:把任务定义、执行策略、约束条件三者解耦。你只需要关心业务逻辑,调度的事情交给框架。

我个人觉得,它最大的价值在于「可靠性」。以前用 Service 做后台任务,总担心被系统杀掉。现在有了 WorkManager,我可以放心地说:「这个任务一定会被执行」。

嗯,如果你还在用老方式做后台任务,不妨试试 WorkManager。你会发现,原来后台调度可以这么省心。


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