WorkManager:可靠的后台任务调度

WorkManager:可靠的后台任务调度

背景

Android 后台任务一直是个大坑。早期的 Service、AlarmManager、JobScheduler、Firebase JobDispatcher......一套组合拳下来,开发者要自己处理 Doze 模式、API 版本差异、任务持久化、网络切换重试,写出来的代码又长又容易出 bug。

WorkManager 是 Jetpack 提供的统一后台任务调度方案。它在底层自动选择合适的调度器------API 23 以上用 JobScheduler,以下用 AlarmManager + BroadcastReceiver------但对外暴露的是完全一致的 API。你只需要定义"做什么"和"什么时候做",WorkManager 负责保证任务最终执行,即使应用退出或设备重启后也能恢复。

一句话:WorkManager 适合那些"必须执行但不需要立刻执行"的任务,比如日志上传、数据库清理、定期同步等。

核心概念

Worker:任务的执行单元

Worker 是一个抽象类,你需要在 doWork() 里写真正要执行的逻辑,最后返回 Result.success()Result.failure()Result.retry()

kotlin 复制代码
class UploadLogWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        return try {
            // 执行上传逻辑
            Result.success()
        } catch (e: Exception) {
            Result.retry() // 失败时可重试
        }
    }
}

WorkRequest:任务的调度指令

WorkRequest 告诉 WorkManager 这个任务怎么跑。分两种:

  • OneTimeWorkRequest:一次性任务
  • PeriodicWorkRequest:定期任务(最小间隔 15 分钟)

两者都支持 Constraints(约束条件),比如"仅在联网时执行""仅在充电时执行""设备空闲时执行"。

WorkManager 的调度保证

WorkManager 不保证精确的执行时间。它保证的是"任务一定会被完成",即便进程被杀、设备重启。任务的入参和状态会被序列化保存到内部数据库,重启后自动恢复。

代码实战(Kotlin)

1. 添加依赖

kotlin 复制代码
// build.gradle.kts (module)
dependencies {
    implementation("androidx.work:work-runtime-ktx:2.10.0")
}

2. 定义 Worker

kotlin 复制代码
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters

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

    override fun doWork(): Result {
        val userId = inputData.getString("user_id") ?: return Result.failure()
        Log.d("SyncDataWorker", "开始同步用户 $userId 的数据")

        return try {
            // 模拟网络同步
            Thread.sleep(2000)
            Log.d("SyncDataWorker", "同步完成")
            Result.success()
        } catch (e: Exception) {
            Log.e("SyncDataWorker", "同步失败", e)
            Result.retry()
        }
    }
}

3. 单次任务:带约束的 OneTimeWorkRequest

kotlin 复制代码
import androidx.work.*

val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED) // 仅在联网时执行
    .setRequiresBatteryNotLow(true)                // 电量不低
    .build()

val inputData = Data.Builder()
    .putString("user_id", "42")
    .build()

val uploadRequest = OneTimeWorkRequestBuilder<SyncDataWorker>()
    .setConstraints(constraints)
    .setInputData(inputData)
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)
    .build()

WorkManager.getInstance(context).enqueue(uploadRequest)

setBackoffCriteria 指定重试策略:指数退避,初始延迟 10 秒。前一次返回 Result.retry() 时,WorkManager 会按这个策略延后重试。

4. 定期任务:PeriodicWorkRequest

kotlin 复制代码
val syncRequest = PeriodicWorkRequestBuilder<SyncDataWorker>(
    1, TimeUnit.HOURS // 每小时执行一次
)
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "periodic_sync",
    ExistingPeriodicWorkPolicy.KEEP, // 同名任务已存在则保留旧的
    syncRequest
)

注意:PeriodicWorkRequest 的最小间隔是 15 分钟,低于这个值会抛异常。用 enqueueUniquePeriodicWork 可以防止重复调度。

5. 任务链:按顺序执行

kotlin 复制代码
val cleanDb = OneTimeWorkRequestBuilder<CleanDbWorker>().build()
val syncData = OneTimeWorkRequestBuilder<SyncDataWorker>().build()
val sendReport = OneTimeWorkRequestBuilder<SendReportWorker>().build()

WorkManager.getInstance(context)
    .beginWith(cleanDb)
    .then(syncData)
    .then(sendReport)
    .enqueue()

链中任何一个 Worker 返回 Result.failure(),后续 Worker 都不会执行,整条链终止。

6. 在 ViewModel 中观察任务状态

kotlin 复制代码
import androidx.lifecycle.ViewModel
import androidx.work.WorkInfo
import androidx.work.WorkManager

class UploadViewModel : ViewModel() {

    fun observeWorkStatus(context: Context, workId: UUID) {
        WorkManager.getInstance(context)
            .getWorkInfoByIdLiveData(workId)
            .observeForever { info: WorkInfo? ->
                when (info?.state) {
                    WorkInfo.State.ENQUEUED -> println("任务已入队")
                    WorkInfo.State.RUNNING -> println("任务执行中")
                    WorkInfo.State.SUCCEEDED -> println("任务成功")
                    WorkInfo.State.FAILED -> println("任务失败")
                    WorkInfo.State.BLOCKED -> println("任务被阻塞(等待前置任务)")
                    WorkInfo.State.CANCELLED -> println("任务已取消")
                    else -> {}
                }
            }
    }
}

避坑指南

不要把 WorkManager 当 AlarmManager 用。 WorkManager 的最小调度间隔是 15 分钟,且无法保证精确到秒。如果需要精确闹钟或秒级周期性任务,应该用 AlarmManager 或 Handler。

doWork 在主线程外执行,但 Worker 默认在后台线程池。 如果用 CoroutineWorker(继承自 ListenableWorker),doWork 是 suspend 函数,默认在 Dispatchers.Default;如果用普通 Worker,doWork 在后台线程执行,不能直接操作 UI。

输入输出数据有大小限制。 Data 对象底层用 Bundle 序列化,建议只传简单键值对。大数据应该存到文件或数据库,Worker 里再读取。

注意 PeriodicWorkRequest 的最小间隔。 文档写 15 分钟是硬约束。如果想 5 分钟执行一次,可以考虑 OneTimeWorkRequest + 递归调度,但要自己处理幂等性。

WorkManager 初始化。 高版本 Jetpack 默认通过 ContentProvider 自动初始化。如果需要自定义配置(比如自定义线程池),要在 AndroidManifest.xml 中禁用自动初始化,改用手动初始化。

总结

WorkManager 在 Android 后台任务体系中占据了一个明确的位置:它不做实时推送、不搞精确定时,但它把"可靠性"做到了极致。只要任务被入队,即便应用进程死亡、设备重启,WorkManager 也能帮你捡起来继续跑。

实际开发中,记住一个判断法则:如果这个任务"用户离开页面后还需要继续做",而且"必须做完不能丢",那就用 WorkManager。配合周期性调度和约束条件,它比手写 Service 更省心,比 AlarmManager 更可靠。掌握之后,你会发现自己写后台逻辑的底气足了很多。

相关推荐
Setsuna_F_Seiei18 小时前
前端转型 Agent 开发 04 之 MCP 与 Skill(赋予 Agent 更广工作能力)
前端·agent
刘发财20 小时前
前端2秒生成500页矢量PDF,rust真的强到没朋友
前端·javascript·rust
郑州光合科技余经理20 小时前
国际版外卖系统:税率字段怎么和订单主流程解耦
android·java·开发语言·前端·后端·php·ai编程
梦想平凡21 小时前
百游棋牌源代码开发搭建教程(十):隔离部署、备份恢复与双端验收
java·前端·javascript·数据库·源代码管理
yume_sibai1 天前
06-Rust Web 开发实战(Axum 框架 + 数据库 + JWT 认证 + 中间件 + 部署)
前端·数据库·rust
计算机魔术师1 天前
特朗普上台打给黄仁勋:AI末日论是骗局,我们绝不让它发生
前端
troy1281 天前
Python 基础语法(八):Web 后端开发、数据分析与可视化、网络爬虫、人工智能 / 大模型应用
前端·python·数据分析
计算机魔术师1 天前
CEO说要慢下来,黑客说别做梦了——同一篇论文,两种命运
前端
kyriewen1 天前
我花3天抓了一个幽灵bug,凶手藏在第4层
前端·javascript·程序员
IT_陈寒1 天前
Java里用Stream.parallel()翻车实录,这性能还不如单线程
前端·人工智能·后端