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 更可靠。掌握之后,你会发现自己写后台逻辑的底气足了很多。

相关推荐
To_OC7 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic7 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy7 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志8 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble9 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯10 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜10 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司10 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g11 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森11 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http