4. 后台任务基础概念:WorkManager 适用场景、任务类型与约束条件

Android 提供了多种后台执行方案,但在保证省电、兼容不同系统版本、避免被系统强制杀死的需求下,WorkManager 成为官方推荐的首选。本章将剖析 WorkManager 的定位、任务分类以及约束机制,为后续结合 Room 数据库的实操打下坚实基础。

4.1 适用场景:何时选择 WorkManager

WorkManager 适用于可延迟、需保证完成的后台任务。它与应用生命周期解耦,即使应用被用户关闭或设备重启,任务仍能被调度并执行。

典型场景
  • 数据同步:定期将本地数据库(Room)的未上传记录同步至服务器。
  • 日志上传:在设备连接 Wi-Fi 且电量充足时批量上传崩溃日志。
  • 缓存清理:每天凌晨清理过期的图片缓存。
  • 预取内容:在空闲时预先下载用户可能浏览的新闻列表。
对比项 前台服务 JobScheduler WorkManager
系统兼容性 API 1+ API 21+ API 14+(通过兼容包)
持久化保证 不保证 设备重启后任务可能丢失 数据持久化至 Room,重启后恢复
约束条件 需手动检查 内置网络/充电约束 网络、充电、空闲、存储等丰富约束
延迟执行 不支持 支持最小延迟 支持最小延迟 + 灵活重试策略

4.2 任务类型:OneTimeWorkRequest 与 PeriodicWorkRequest

WorkManager 将任务分为两种基本类型,分别对应一次性和周期性的需求。

4.2.1 OneTimeWorkRequest(一次性任务)

只执行一次,适用于“上传单条日志”、“执行一次数据清洗”等场景。

// 创建一次性上传任务
OneTimeWorkRequest uploadWork =
    new OneTimeWorkRequest.Builder(UploadWorker.class)
        .setInitialDelay(10, TimeUnit.MINUTES)  // 延迟 10 分钟后执行
        .setConstraints(constraints)            // 设置约束条件
        .addTag("upload")
        .build();

WorkManager.getInstance(context).enqueue(uploadWork);

4.2.2 PeriodicWorkRequest(周期性任务)

按固定间隔重复执行,适用于“每天同步一次数据库”、“每 15 分钟检查一次新消息”等场景。
注意:系统为了省电会将多个周期任务合并,实际执行时间可能偏离设置值。

// 创建每天执行一次的同步任务
PeriodicWorkRequest syncWork =
    new PeriodicWorkRequest.Builder(SyncWorker.class, 24, TimeUnit.HOURS)
        .setConstraints(constraints)
        .addTag("sync")
        .build();

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "daily_sync",
    ExistingPeriodicWorkPolicy.KEEP,
    syncWork
);
参数 OneTimeWorkRequest PeriodicWorkRequest
执行次数 1 次 无限循环(需手动取消)
最小间隔 无限制 15 分钟(实际可能更长)
延迟设置 setInitialDelay 不支持(固定间隔)
重试策略 背压、指数退避 下一次窗口重新尝试

4.3 约束条件设置:让任务在合适时机执行

约束条件(Constraints)是 WorkManager 的核心特性,它允许开发者声明任务运行的环境要求,避免在弱网、低电量等恶劣条件下抢占资源。

4.3.1 可用约束类型

Constraints constraints = new Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)      // 需要网络连接
    .setRequiresBatteryNotLow(true)                      // 电池不能过低
    .setRequiresCharging(true)                           // 正在充电
    .setRequiresDeviceIdle(true)                         // 设备处于空闲状态(API 23+)
    .setRequiresStorageNotLow(true)                      // 存储空间充足
    .build();
约束条件 方法签名 说明
网络类型 setRequiredNetworkType(NetworkType) NOT_REQUIRED / CONNECTED / UNMETERED / NOT_ROAMING / METERED
电量不低于阈值 setRequiresBatteryNotLow(true) 避免在电量低于 15% 时执行
充电状态 setRequiresCharging(true) 仅在连接充电器时执行
设备空闲 setRequiresDeviceIdle(true) API 23+;设备待机且屏幕关闭时执行
存储空间 setRequiresStorageNotLow(true) 内置存储剩余空间充足
约束冲突处理:若任务同时设置网络连接和充电约束,则两个条件必须同时满足才会执行。WM 会等待所有条件满足,不会因条件不满足而取消任务。

4.3.2 调试约束状态

在开发阶段可以通过 WorkManager.getWorkInfoByIdLiveData() 观察任务状态,确认约束是否触发。

WorkManager.getInstance(context)
    .getWorkInfoByIdLiveData(uploadWork.getId())
    .observe(this, workInfo -> {
        if (workInfo != null) {
            Log.d("WM_Debug", "状态: " + workInfo.getState());
            Log.d("WM_Debug", "约束是否满足: " + workInfo.getState().isFinished());
        }
    });

4.4 任务流程与状态机

下面的 SVG 展示了一个 WorkRequest 从创建到完成的完整生命周期,以及 OneTimeWork 和 PeriodicWork 的分支路径。

WorkManager 任务调度流程 WorkManager 任务调度流程 创建 WorkRequest 约束条件检查 网络/充电/空闲/存储 不满足 等待条件满足 任务类型判断 一次性 周期性 执行一次 按间隔循环执行 SUCCESS / FAILURE 等待下一周期 约束不满足时 任务进入 ENQUEUED 状态等待

4.5 常见错误与调试

错误 1:约束设置过于严苛导致任务永不执行
例如同时设置 CONNECTED、充电、空闲 三项。在模拟器中测试时,需手动满足所有条件,否则任务将一直处于 ENQUEUED 状态。建议开发阶段先用 Constraints.NONE 验证业务逻辑,再逐步添加约束。
// 调试:查看排队中的任务
adb shell dumpsys jobscheduler | grep "your.package.name"

4.6 性能与稳定性优化建议

  • 避免短周期任务:PeriodicWorkRequest 最小间隔为 15 分钟,若需更短间隔,应结合前台服务或 AlarmManager。(WorkManager 官方不推荐小于 15 分钟)
  • 使用唯一任务策略:对于同类型任务,使用 enqueueUniqueWork 或 enqueueUniquePeriodicWork 防止重复排队。
  • 合理设置重试策略:对于网络相关的任务,使用 BackoffPolicy 设置指数退避,避免频繁重试消耗电量。
// 设置重试策略:线性退避,最多重试 3 次
OneTimeWorkRequest retryWork = new OneTimeWorkRequest.Builder(UploadWorker.class)
    .setBackoffCriteria(
        BackoffPolicy.LINEAR,
        OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
        TimeUnit.MILLISECONDS)
    .build();