首页 / Android 入门教程 / WorkManager

Android 入门教程

WorkManager

本教程共 100 篇 · 第 75 篇 · 更新于 2026-07-28 · 约 10 分钟阅读

AndroidAndroid 入门教程WorkManagerJetpack后台任务协程

75. WorkManager

本节目标:学会用 WorkManager 调度可靠执行的后台任务,掌握 Worker 定义、约束条件、周期任务、链式任务、加急工作,理解它和协程的分工。

协程解决「前台时」的并发,但用户切到后台、应用被杀,协程就没了。要保证任务一定执行,得用 WorkManager。

WorkManager 是什么

WorkManager 是 Jetpack 提供的后台任务调度库。它的核心承诺:

  • 可靠执行:应用退出、设备重启,任务照样跑。
  • 约束触发:只在充电、联网、空闲等条件下运行。
  • 自动重试:失败自动退避重试。
  • 任务链:多个任务串联、并联,传递数据。

打个比方,协程像你亲自盯着的快递,你走了快递就停;WorkManager 像物流公司,你下了单就管走人,包裹会按约定送到。

适用场景

适合 WorkManager 的任务:

  • 定期同步服务器数据。
  • 上传日志、埋点。
  • 处理用户产生的内容(拍照后上传)。
  • 夜间清理缓存。

不适合的场景:

  • 用户在前台时立即响应的操作(用协程)。
  • 必须立即执行不可中断的操作(用前台服务)。
  • 精确时间点触发(用 AlarmManager)。

第一步:加依赖

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

KTX 版本自带协程支持,比纯 work-runtime 好用。

第二步:定义 Worker

Worker 是任务的具体实现。继承 WorkerCoroutineWorker,推荐后者,能用协程:

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

    override suspend fun doWork(): Result {
        return try {
            val fileUri = inputData.getString("file_uri") ?: return Result.failure()
            val file = Uri.parse(fileUri).toFile()
            uploadApi.upload(file)
            Result.success()
        } catch (e: Exception) {
            if (runAttemptCount < 3) {
                Result.retry()
            } else {
                Result.failure()
            }
        }
    }
}

返回值三档:

  • Result.success():成功,结束。
  • Result.failure():失败,不重试,结束。
  • Result.retry():失败,按退避策略重试。
Note

CoroutineWorkerdoWork 默认在 Dispatchers.IO 跑,不用自己切线程。

第三步:构造 WorkRequest

Worker 是「做什么」,WorkRequest 是「怎么做、什么时候做」。

一次性任务

val request = OneTimeWorkRequestBuilder<UploadWorker>()
    .setInputData(workDataOf("file_uri" to fileUri.toString()))
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .setBackoffCriteria(
        BackoffPolicy.EXPONENTIAL,
        10, TimeUnit.SECONDS
    )
    .build()

WorkManager.getInstance(context).enqueue(request)

周期任务

val periodicRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.DAYS)
    .setConstraints(
        Constraints.Builder()
            .setRequiresCharging(true)
            .setRequiredNetworkType(NetworkType.UNMETERED)
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "daily_sync",
    ExistingPeriodicWorkPolicy.KEEP,
    periodicRequest
)

要点:

  • 周期最短 15 分钟,系统强制。
  • enqueueUniquePeriodicWork 用唯一名字,避免重复调度。KEEP 表示已有同名任务就保留旧的,REPLACE 表示替换。
  • 周期任务的执行时间不精确,受 Doze 影响,可能延迟。
Warning

WorkManager 不保证精确执行时间。需要 7 点整触发的闹钟应用,用 AlarmManager。

约束条件

Constraints 决定任务在什么条件下才跑:

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)  // 联网
    .setRequiresCharging(true)                       // 充电中
    .setRequiresBatteryNotLow(true)                  // 电量不低
    .setRequiresStorageNotLow(true)                  // 存储不低
    .setRequiresDeviceIdle(true)                     // 设备空闲
    .build()

NetworkType 几档:

  • NOT_REQUIRED:不限制。
  • CONNECTED:任何网络。
  • UNMETERED:Wi-Fi 等不计流量网络。
  • METERED:计流量网络。
  • NOT_ROAMING:非漫游。

约束满足才会触发,没满足一直等。

加急工作

普通 WorkManager 任务可能被延迟很久。某些任务用户感知强、需要立即执行但能几分钟内完成,用「加急工作」(Expedited Work):

val request = OneTimeWorkRequestBuilder<UploadWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

特点:

  • 优先级高,系统尽快调度。
  • 有配额限制,超过配额退化成普通任务(或者直接失败,看策略)。
  • Android 12+ 用「加急作业」机制实现。
  • 适合「用户点了上传,希望立刻跑」的场景。
Tip

加急工作配额按应用前台时间分配。前台用得多的应用配额多。别滥用,留给真正重要的任务。

链式任务

多个任务有依赖关系,用 beginWith + then

WorkManager.getInstance(context)
    .beginWith(OneTimeWorkRequest.from(FilterWorker::class.java))
    .then(OneTimeWorkRequest.from(CompressWorker::class.java))
    .then(OneTimeWorkRequest.from(UploadWorker::class.java))
    .enqueue()

每个 Worker 的输出数据会自动传给下一个:

// FilterWorker
val output = workDataOf("filtered_uri" to filteredUri.toString())
Result.success(output)

// CompressWorker
val input = inputData.getString("filtered_uri")

并联任务:

WorkManager.getInstance(context)
    .beginWith(workA, workB)  // A 和 B 并行
    .then(workC)                     // 都完成后跑 C
    .enqueue()

或者用 WorkContinuation.combine

val chain1 = WorkManager.getInstance(context).beginWith(workA).then(workB)
val chain2 = WorkManager.getInstance(context).beginWith(workC).then(workD)
WorkContinuation.combine(listOf(chain1, chain2))
    .then(workE)
    .enqueue()

唯一任务

避免重复调度,用唯一名字:

WorkManager.getInstance(context).enqueueUniqueWork(
    "upload_avatar",
    ExistingWorkPolicy.REPLACE,
    request
)

策略:

  • REPLACE:取消旧任务,启动新的。
  • KEEP:保留旧任务,新的丢弃。
  • APPEND:追加到旧任务后面,形成链。

观察任务状态

任务入队后能观察状态:

WorkManager.getInstance(context)
    .getWorkInfoByIdFlow(request.id)
    .collect { info ->
        when (info?.state) {
            WorkInfo.State.ENQUEUED -> showPending()
            WorkInfo.State.RUNNING -> showProgress()
            WorkInfo.State.SUCCEEDED -> showDone()
            WorkInfo.State.FAILED -> showError()
            WorkInfo.State.CANCELLED -> showCancelled()
            else -> {}
        }
    }

也可以按 tag 或者 name 查:

WorkManager.getInstance(context)
    .getWorkInfosByTagFlow("upload")
WorkManager.getInstance(context)
    .getWorkInfosForUniqueWorkFlow("upload_avatar")

取消任务

按 id、tag、name 取消:

WorkManager.getInstance(context).cancelWorkById(request.id)
WorkManager.getInstance(context).cancelAllWorkByTag("upload")
WorkManager.getInstance(context).cancelUniqueWork("upload_avatar")

取消是协作式的,Worker 里的 doWork 会收到 CancellationException,要在合适的地方退出。

前台服务 Worker

长时间运行的任务(超过 10 分钟)需要变成前台服务:

class LongRunningWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        setForeground(
            ForegroundInfo(
                NOTIFICATION_ID,
                buildNotification("正在处理...")
            )
        )
        // 长时间任务
        return Result.success()
    }
}
Warning

Android 14+ 前台服务 Worker 要声明服务类型。比如数据同步类型要申请 FOREGROUND_SERVICE_DATA_SYNC 权限,并在 ForegroundInfo 里指定类型。

输入输出数据

Data 对象在任务间传值,只能存基本类型和字符串:

val input = workDataOf(
    "user_id" to 123L,
    "file_path" to "/sdcard/x.jpg",
    "is_urgent" to true
)

val output = workDataOf("result_url" to "https://...")
Note

Data 有大小限制(约 1KB),别用来传大数据。要传文件用 URI,Worker 里读文件。

测试 Worker

Worker 单元测试用 TestWorkerBuilder

@Test
fun `upload success`() = runTest {
    val worker = TestWorkerBuilder.from(context, UploadWorker::class.java).build()
    val result = worker.doWork()
    assertThat(result).isEqualTo(Result.success())
}
Tip

Worker 逻辑要写得可测—把网络、文件操作抽象成依赖注入到 Worker 构造函数,测试时换 Mock。

常见坑

  1. 周期任务设短了:最短 15 分钟,写 1 分钟不会按你设的跑。
  2. 约束太严setRequiresDeviceIdle(true) 在日常使用中很难满足,任务可能几天不跑。
  3. 加急工作滥用:配额用光,真正紧急任务跑不了。
  4. 大对象塞 Data:超 1KB 报错,用 URI 替代。
  5. Worker 里调主线程操作:Worker 在子线程,操作 UI 要 post 回主线程。
  6. 忘了唯一名字:每次启动都 enqueue,任务越堆越多。周期任务必须用 enqueueUniquePeriodicWork

小结

WorkManager 适合「需要可靠执行」的后台任务。CoroutineWorker + Constraints + WorkRequest 三件套搞定大部分场景。一次性任务用 OneTimeWorkRequest,周期任务用 PeriodicWorkRequest,紧急任务加 setExpedited,长任务用 setForeground。任务间用 Data 传值,链式任务用 beginWith().then()

下一章讲其他异步方案,把 Handler、线程池这些历史方案补齐。