WorkManager:可靠的延迟与后台任务调度

FreeGuideOnline 最新 2026-06-17

认识 WorkManager:让后台任务可靠又简单

在 Android 开发中,处理后台任务一直是一个需要小心翼翼应对的领域。从早期的 ServiceJobScheduler,到后来的 AlarmManagerFirebase JobDispatcher……开发者面对碎片化的 API、复杂的电池优化与后台限制,经常感到头疼。Google 推出了 Jetpack WorkManager 库,致力于统一解决需要保证执行、即便应用退出或设备重启也不丢失的延迟后台任务

WorkManager 能解决什么问题?

许多任务不一定要立刻执行,但必须保证最终完成

  • 上传日志或错误报告
  • 定期同步服务器数据
  • 压缩并保存用户拍摄的图片
  • 在满足特定条件(如网络可用)时下载资源
  • 应用更新后的数据库整理

对于这类任务,如果依赖简单的 ThreadAsyncTask,应用被杀死任务就丢掉了;如果依赖 Service,又容易受系统后台限制影响,消耗电量。WorkManager 在底层根据设备 API 版本自动选择合适的调度引擎(JobSchedulerAlarmManager 等),并提供统一 API、约束条件、链式执行、状态观察等特性,让开发者只需关注做什么,不用管怎么保证执行

核心优势一览

  • 保证任务执行:即使应用进程被杀死,任务也会在合适的时候重新调度。
  • 兼容性强:自动适配 API 14 以上的所有设备,最低支持到 Android 4.0。
  • 约束支持:可以指定只在「设备空闲」「网络已连接」「电量充足」等条件满足时才运行。
  • 链式任务:可以定义任务执行顺序,或组合出复杂的依赖关系(A → B → C)。
  • 状态观察:通过 LiveDataListenableFuture 方便地观察任务进度和结果。
  • 定期任务:灵活设置最小间隔的定期工作,系统会优化定时执行的时机。
  • 加急任务:从 WorkManager 2.7 开始支持加急工作,可绕过省电模式的限制执行重要任务。

快速上手:集成与第一行代码

添加依赖

在模块级 build.gradle.kts 中添加最新稳定版:

dependencies {
    val work_version = "2.9.0"
    implementation("androidx.work:work-runtime-ktx:$work_version")
}

如果你的项目使用 Java,把 -ktx 去掉即可。推荐配合 Kotlin 获得更简洁的协程支持。

定义一个 Worker

创建一个类继承 Worker,重写 doWork() 方法,在这里编码实际要执行的逻辑,并返回执行结果:

class UploadLogWorker(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {

    override fun doWork(): Result {
        try {
            // 模拟上传日志
            uploadLogs()
            // 任务成功完成
            return Result.success()
        } catch (e: Exception) {
            // 任务失败,如果需要重试可以返回 Result.retry()
            return Result.failure()
        }
    }
}

返回结果有三种:

  • Result.success():成功。
  • Result.failure():失败,且不需要重试。
  • Result.retry():暂时失败,WorkManager 会根据退避策略稍后重试。

构建并提交任务

使用 WorkRequest 来描述任务,并提交给 WorkManager 实例:

val uploadRequest = OneTimeWorkRequestBuilder<UploadLogWorker>().build()
WorkManager.getInstance(context).enqueue(uploadRequest)

这样,任务就进入系统调度队列了。即使此刻网络断掉,只要约束条件满足,它就会执行。

给任务加上约束:只在合适时机运行

很多后台操作需要特定的环境才执行,比如上传必须连网,下载最好在 Wi‑Fi 下进行。通过 Constraints 可以轻松设置:

val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)   // 任意网络连接
    // .setRequiredNetworkType(NetworkType.UNMETERED) // 仅非计费网络(Wi-Fi)
    .setRequiresBatteryNotLow(true)                  // 电池不低
    .setRequiresCharging(true)                       // 充电中
    .setRequiresDeviceIdle(true)                     // 设备空闲(API 23+)
    .setRequiresStorageNotLow(true)                  // 存储空间不低
    .build()

val constrainedRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
    .setConstraints(constraints)
    .build()

当约束不再满足时,任务会自动停止,待条件恢复后继续执行。

一次性任务与定期任务

WorkManager 支持两种基础调度类型。

一次性任务 (OneTimeWorkRequest)

适合执行一次且保证完成的工作。可以通过 setInitialDelay() 延迟执行:

val delayedRequest = OneTimeWorkRequestBuilder<SyncWorker>()
    .setInitialDelay(10, TimeUnit.MINUTES)
    .build()

定期任务 (PeriodicWorkRequest)

适合每隔一段时间重复执行的操作,如定期同步。最小间隔为 15 分钟(这是系统省电所要求的):

val periodicRequest = PeriodicWorkRequestBuilder<CleanupWorker>(
    repeatInterval = 15, TimeUnit.MINUTES   // 循环间隔
).build()

注意定期任务不能设置初始延迟,但同样可以应用约束。

链式任务与执行策略

串行链式调用

如果想按顺序执行一系列任务,一个任务成功后自动执行下一个,可以使用 beginWiththen

WorkManager.getInstance(context)
    .beginWith(compressImageWork)
    .then(uploadImageWork)
    .then(cleanCacheWork)
    .enqueue()

任何一个失败,后续的任务都不会执行。这种依赖关系在频繁组合数据流时非常有用。

并行任务与复杂组合

可以使用 beginWith 传入多个 WorkRequest,它们会并行执行。然后用 then 接后续任务:

val parallelTaskA = OneTimeWorkRequestBuilder<TaskA>().build()
val parallelTaskB = OneTimeWorkRequestBuilder<TaskB>().build()
val mergeTask = OneTimeWorkRequestBuilder<MergeTask>().build()

WorkManager.getInstance(context)
    .beginWith(listOf(parallelTaskA, parallelTaskB))
    .then(mergeTask)
    .enqueue()

并行任务全部成功后,mergeTask 才会启动。

唯一工作序列

有时需要保证同一时间只有一个同名链在执行,例如同步操作只应有一个实例,可以使用 beginUniqueWork

WorkManager.getInstance(context)
    .beginUniqueWork(
        "unique_sync",
        ExistingWorkPolicy.KEEP,            // 如果已有同名任务在运行,保留原有的,不重复创建
        syncRequest
    )
    .enqueue()

ExistingWorkPolicy 选项:

  • REPLACE:取消已存在的链,用新的替换。
  • KEEP:保留已存在的链,忽略新请求。
  • APPEND:将新请求附加到已有链的末尾。

观察任务状态与进度

很多时候我们需要在界面展示进度或根据结果做出响应。WorkManager 为每个请求都提供了状态信息,通过 LiveDataListenableFuture 可以轻松监听。

获取 WorkInfo

每个 WorkRequest 都有一个唯一 ID,通过它获取 LiveData<WorkInfo>

val workInfoLiveData = WorkManager.getInstance(context)
    .getWorkInfoByIdLiveData(uploadRequest.id)

workInfoLiveData.observe(lifecycleOwner) { workInfo ->
    if (workInfo != null) {
        when (workInfo.state) {
            WorkInfo.State.ENQUEUED -> showStatus("已加入队列")
            WorkInfo.State.RUNNING -> {
                // 还可以获取进度数据
                val progress = workInfo.progress.getInt("PROGRESS", 0)
                updateProgressBar(progress)
            }
            WorkInfo.State.SUCCEEDED -> showStatus("上传成功")
            WorkInfo.State.FAILED -> showStatus("上传失败")
            WorkInfo.State.BLOCKED -> showStatus("被阻塞,等待依赖任务")
            WorkInfo.State.CANCELLED -> showStatus("已取消")
        }
    }
}

在 Worker 中报告进度

doWork() 内使用 setProgress() 发送进度数据:

class ProgressWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        for (i in 0..100 step 10) {
            setProgress(workDataOf("PROGRESS" to i))
            // 模拟耗时操作
            Thread.sleep(500)
        }
        return Result.success()
    }
}

然后观察端就能实时收到进度数值。

输入输出数据传递

任务经常需要接收参数,并返回处理结果。WorkManager 使用 Data 对象来传递轻量数据(类似于 Bundle,支持基本类型及小尺寸数据)。

传入输入数据

创建 WorkRequest 时设置输入:

val inputData = workDataOf(
    "IMAGE_URI" to "file:///sdcard/photo.jpg",
    "COMPRESS_QUALITY" to 80
)

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

Worker 中取出:

val imageUri = inputData.getString("IMAGE_URI")
val quality = inputData.getInt("COMPRESS_QUALITY", 80)

返回输出数据

doWork() 中通过 Result.success(outputData) 返回:

return Result.success(workDataOf("OUTPUT_URI" to compressedUri))

后续任务可以通过 getInputData() 获取前一个任务的输出,实现任务间数据流串联。

加急任务:突破省电限制

从 Android 12 起,后台工作受到了更严格限制。但某些任务对时效性要求高(如消息发送、支付同步),可以使用加急工作 (Expedited Work)。WorkManager 2.7+ 提供了 setExpedited()

val importantRequest = OneTimeWorkRequestBuilder<PaymentSyncWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

加急任务会尽可能快地执行,但系统仍可能因资源问题将其稍作延迟。如果应用超出加急配额,可以降级为普通任务(参数指定的策略)。

取消、停止与自定义重试

取消任务

通过 ID 或标签取消任务:

WorkManager.getInstance(context).cancelWorkById(request.id)
// 或者给任务加标签,批量取消
WorkManager.getInstance(context).cancelAllWorkByTag("sync_tag")

重试与退避策略

doWork() 返回 Result.retry() 时,任务会根据退避策略重新调度。可以自定义退避时间和方式:

val retryRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setBackoffCriteria(
        BackoffPolicy.EXPONENTIAL,
        10L, TimeUnit.SECONDS            // 首次重试间隔10秒
    )
    .build()

两种策略:

  • EXPONENTIAL:指数增长(10秒 → 20秒 → 40秒……)。
  • LINEAR:固定间隔。

调试与诊断

在开发阶段查看任务调度情况非常有用,可以通过 ADB 命令操作:

# 查看当前所有待执行和正在执行的任务信息
adb shell dumpsys jobscheduler | grep -A30 work

# 直接触发一次 WorkManager 的任务(需要知道包名)
adb shell am broadcast -a "androidx.work.impl.diagnostics.REQUEST_DIAGNOSTICS" -p <你的包名>

启用日志也能看到 WorkManager 的内部调度信息:

adb shell setprop log.tag.WM-EXECUTOR VERBOSE
adb shell setprop log.tag.WM-SCHEDULER VERBOSE

最佳实践与常见问题

  1. Worker 的小心陷阱
    doWork() 默认在后台线程运行(Worker 使用一个共享线程池),不要在内部直接更新 UI。如需更新 UI,请使用 Post 到主线程或结合 LiveData

  2. 避免内存泄漏
    观察 WorkInfo 时,尽量使用 LiveData + LifecycleOwner 自动管理生命周期。如果不需要后台结果响应,可以不观察。

  3. 定期任务间隔选择
    系统可能会为了省电将多个定期任务挤在一起执行。如果你的操作不需要严格的准时,接受系统弹性调度会更省电。

  4. 长时间运行任务
    doWork() 方法被期望在 10 分钟内完成。如果确实需要长时间运行,可以考虑 ForegroundService 配合 WorkManager 的加急任务,或使用 WorkManagersetForeground() 信息。

  5. 处理停止信号
    你的 Worker 在系统要求停止时会收到 onStopped() 回调。重写它以释放资源或标记取消,使任务提前结束,避免继续浪费资源。

小结

WorkManager 是 Android 当下处理可靠后台任务的推荐方案。它统一了碎片化的 API,提供了强大的约束、链式组合和状态观察功能,并且能保证任务在应用退出、设备重启后依然得到执行。从简单的一次性上传,到复杂的数据同步链,都可以依赖 WorkManager 让您的应用更加健壮和节能。

开始尝试将那些需要“保证完成”的逻辑迁移到 WorkManager 中吧,您会惊喜于它的稳定和简洁!