第 18 章JAVA45 分钟
WorkManager 任务调度
掌握 WorkManager 的优势、OneTimeWorkRequest/PeriodicWorkRequest、Constraints(网络/充电/空闲)、链式任务(then/combine)、Worker 类实现、输入输出数据与测试
学习目标
- 理解 WorkManager 相对 Service 与 JobScheduler 的优势与适用场景
- 用 OneTimeWorkRequest 与 PeriodicWorkRequest 调度单次/周期任务
- 用 Constraints 约束网络/充电/空闲/存储空间
- 构建 then 链式与 combine 并行组合的任务图
- 实现自定义 Worker、传递输入输出数据并完成单元测试
学习目标
WorkManager 是 Android Jetpack 提供的“可持久化、可约束、可链式”的任务调度库,自动根据设备 API 与状态选择 JobScheduler / AlarmManager / BroadcastReceiver 实现。本章目标:
- 理解 WorkManager 替代 Service / JobScheduler / AlarmManager 的优势;
- 用
OneTimeWorkRequest与PeriodicWorkRequest调度任务; - 用
Constraints约束网络、充电、空闲、存储空间; - 用
then与combine构建串行/并行任务图; - 实现自定义 Worker、传递输入输出数据,并完成单元测试。
WorkManager 优势
| 特性 | Service | JobScheduler | WorkManager |
|---|---|---|---|
| 任务持久化 | 否 | API 23+ | 是(进程被杀后任务保留) |
| 重启后任务恢复 | 否 | 是 | 是 |
| 约束条件 | 手动判断 | API 23+ | 全版本统一 |
| 链式组合 | 手动 | 部分 | 内置 |
| API 兼容范围 | 全部 | API 23+ | API 14+ |
| 后台执行限制豁免 | 否 | 是 | 是 |
适用场景:
- 上传/下载文件:可中断可恢复;
- 本地数据同步:要求 WiFi、充电时执行;
- 日志上报、清理缓存:周期性任务;
- 业务流程编排:A 完成后执行 B,B 失败后回滚 A。
不适用:
- 需要立即执行(用户在前台等待)→ 用协程或线程池;
- 需要精确时间(如闹钟)→ 用
AlarmManager(精确闹钟)。
Gradle 依赖
dependencies {
def work_version = "2.9.1"
implementation "androidx.work:work-runtime:$work_version"
// 可选:可选 GCMNetworkManager 兼容(多用于 Google Play 设备优化)
implementation "androidx.work:work-gcm:$work_version"
// 测试
androidTestImplementation "androidx.work:work-testing:$work_version"
}
自定义 Worker
1. 同步 Worker(不推荐耗时操作)
public class CompressWorker extends Worker {
public CompressWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public Result doWork() {
String inputPath = getInputData().getString("input_path");
if (TextUtils.isEmpty(inputPath)) {
return Result.failure();
}
try {
compress(inputPath);
Data output = new Data.Builder()
.putString("result_path", inputPath + ".zip")
.build();
return Result.success(output);
} catch (Exception e) {
return Result.retry();
}
}
private void compress(String path) {
// ...
}
}
2. Coroutine / Rx Worker(推荐异步)
由于本章语言为 Java,下面用 ListenableWorker + ListenableFuture(Guava)实现异步:
implementation "androidx.work:work-runtime:$work_version"
implementation "com.google.guava:guava:33.3.1-android"
public class UploadWorker extends ListenableWorker {
public UploadWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public ListenableFuture<Result> startWork() {
return CallbackToFutureAdapter.getFuture(resolver -> {
String filePath = getInputData().getString("file_path");
UploadTask task = new UploadTask(filePath, new UploadTask.Callback() {
@Override
public void onSuccess(String url) {
Data output = new Data.Builder()
.putString("result_url", url)
.build();
resolver.set(Result.success(output));
}
@Override
public void onFailure() {
resolver.set(Result.retry());
}
});
// 持有引用以便取消
resolver.addCancellationListener(task::cancel, Runnable::run);
task.start();
return task;
});
}
}
OneTimeWorkRequest
1. 构建请求
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // WiFi
.setRequiresCharging(true)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.build();
Data input = new Data.Builder()
.putString("file_path", "/sdcard/file.jpg")
.putLong("file_size", 102400)
.build();
OneTimeWorkRequest uploadWork = new OneTimeWorkRequest.Builder(UploadWorker.class)
.setConstraints(constraints)
.setInputData(input)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.setInitialDelay(10, TimeUnit.MINUTES)
.addTag("upload")
.build();
2. 入队与观察
WorkManager.getInstance(this)
.enqueue(uploadWork);
// 观察
WorkManager.getInstance(this)
.getWorkInfoByIdLiveData(uploadWork.getId())
.observe(this, workInfo -> {
if (workInfo != null) {
if (workInfo.getState() == WorkInfo.State.SUCCEEDED) {
String url = workInfo.getOutputData().getString("result_url");
// ...
} else if (workInfo.getState() == WorkInfo.State.FAILED) {
// ...
}
}
});
// 取消
WorkManager.getInstance(this).cancelWorkById(uploadWork.getId());
3. 唯一任务
WorkManager.getInstance(this).enqueueUniqueWork(
"unique_upload", ExistingWorkPolicy.KEEP, uploadWork);
ExistingWorkPolicy 选项:
REPLACE:替换旧任务;KEEP:若已有同名任务在排队则保留旧的;APPEND:追加(不取消旧的);APPEND_OR_REPLACE:追加并允许替换已完成的同名任务。
PeriodicWorkRequest
PeriodicWorkRequest syncWork = new PeriodicWorkRequest.Builder(
SyncWorker.class, 15, TimeUnit.MINUTES)
.setConstraints(constraints)
.addTag("periodic_sync")
.build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"periodic_sync",
ExistingPeriodicWorkPolicy.KEEP,
syncWork);
注意:最小周期为 15 分钟,不能更短。
setInitialDelay可用于“首次延迟”。
链式任务
1. 串行:then
WorkManager.getInstance(this)
.beginWith(filterWorker)
.then(compressWorker)
.then(uploadWorker)
.enqueue();
链中前一个 Worker 的输出会自动作为下一个的输入。
2. 并行:combine
Continuation chain = WorkManager.getInstance(this)
.beginWith(Arrays.asList(filterWorkerA, filterWorkerB))
.then(combineWorker);
chain.enqueue();
两个 filter 并行执行,全部成功后才进入 combineWorker。
3. 复杂图:结合 then 与 combine
WorkContinuation left = WorkManager.getInstance(this)
.beginWith(filterWorkerA).then(compressWorkerA);
WorkContinuation right = WorkManager.getInstance(this)
.beginWith(filterWorkerB).then(compressWorkerB);
WorkContinuation.combine(Arrays.asList(left, right))
.then(uploadWorker)
.enqueue();
4. 唯一链
WorkManager.getInstance(this)
.beginUniqueWork("image_pipeline",
ExistingWorkPolicy.KEEP,
filterWorker)
.then(compressWorker)
.then(uploadWorker)
.enqueue();
输入输出数据
Data 是 WorkManager 传递数据的核心容器,支持基本类型、String 与 Parcelable(受限)。不要传递大对象,Data 有 10KB 限制。
Data input = new Data.Builder()
.putString("user_id", "u_1001")
.putInt("retry_count", 3)
.putBoolean("use_cache", true)
.build();
// Worker 中获取
String userId = getInputData().getString("user_id");
// 返回
Data output = new Data.Builder()
.putString("result_url", "https://...")
.build();
return Result.success(output);
Worker 测试
androidTestImplementation "androidx.work:work-testing:2.9.1"
androidTestImplementation "androidx.test:core:1.6.1"
androidTestImplementation "androidx.test.ext:junit:1.2.1"
1. 同步 Worker 单元测试
@RunWith(AndroidJUnit4.class)
public class CompressWorkerTest {
@Test
public void compress_success() {
Data input = new Data.Builder()
.putString("input_path", "/sdcard/test.jpg")
.build();
TestWorkerBuilder workerBuilder = TestWorkerBuilder.from(
ApplicationProvider.getApplicationContext(),
CompressWorker.class);
workerBuilder.setInputData(input);
CompressWorker worker = (CompressWorker) workerBuilder.build();
ListenableWorker.Result result = worker.startWork().get();
assertTrue(result instanceof ListenableWorker.Result.Success);
}
}
2. WorkManager 测试驱动
@RunWith(AndroidJUnit4.class)
public class WorkFlowTest {
@Before
public void setup() {
Context context = ApplicationProvider.getApplicationContext();
Configuration config = new Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor.create())
.build();
WorkManagerTestInitHelper.initializeTestWorkManager(context, config);
}
@Test
public void chain_runs() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
OneTimeWorkRequest filter = new OneTimeWorkRequest.Builder(FilterWorker.class).build();
OneTimeWorkRequest compress = new OneTimeWorkRequest.Builder(CompressWorker.class).build();
WorkManager.getInstance(context)
.beginWith(filter).then(compress).enqueue().getResult().get();
// TestDriver 可模拟约束满足
}
}
常见坑与最佳实践
- 传递大对象:Data 限制约 10KB。文件路径传 ID,Worker 内查数据库加载。
PeriodicWorkRequest周期 < 15 分钟:会被强制截断为 15 分钟。- 重复
enqueueUniqueWork用错 Policy:KEEP会让旧任务永远不被取消;REPLACE会取消正在进行的任务。按业务谨慎选择。 Result.retry()无限循环:必须配置setBackoffCriteria与最大重试次数(自己实现计数器写入 Data)。- 在 Worker 中持有 Activity/View 引用:Worker 可能跨进程切换,会泄漏。只持有 Application Context。
- 依赖
LiveData观察 Worker:在 Activity 销毁后回调仍执行更新 UI。请绑定到getViewLifecycleOwner()或在 Worker 完成后用 Notification/广播通知。 - 未在 Manifest 注册:WorkManager 默认通过
WorkManagerInitializerContentProvider 自动初始化,无需手动注册。若需自定义初始化要移除 ContentProvider 并调WorkManager.initialize。 - TaskList 太深导致延迟:链式任务过多时调度开销增大,复杂流程建议拆分多个 UniqueWork。
- 测试忘记初始化:必须用
WorkManagerTestInitHelper.initializeTestWorkManager否则WorkManager.getInstance抛异常。 - 后台权限:Android 12+ 部分后台触发需要前台上下文(如后台启动 Activity 限制),WorkManager 在前台 Service 中可豁免部分限制。
章节小结
- WorkManager 提供持久化、约束、链式、API 兼容四大优势,是 Android 后台任务调度首选;
OneTimeWorkRequest处理单次任务,PeriodicWorkRequest处理最小 15 分钟周期任务;Constraints约束网络、充电、空闲、存储、电池;then串行、combine并行,构建复杂任务图;- 自定义 Worker 用
Worker(同步)或ListenableWorker(异步)+ Data 传递输入输出; WorkManagerTestInitHelper+TestWorkerBuilder完成 Worker 单元测试。
下一章预告
第 19 章将讲解 BroadcastReceiver 广播:静态注册(清单)与动态注册(registerReceiver)、标准广播与有序广播、自定义广播、LocalBroadcastManager 废弃后的替代方案、广播权限与最佳实践。