目录一、概述二、核心三大件2.1 定义 Worker 类2.1.1 数据的传递2.1.2 任务的结果2.1.3 停止与取消2.2 创建 WorkRequest2.2.1 任务的类型一次性 vs. 周期性2.2.2 调度配置2.2.3 约束条件2.2.4 输入数据2.2.5 添加优先级2.3 提交任务 WorkManager2.3.1 提交唯一任务2.3.2 获取任务状态(1) 任务状态 (WorkInfo)(2) 通过 ID 获取最精确(3) 通过 Unique Name 获取最常用(4) 通过 Tag 观察任务状态2.3.3 取消任务三、任务链和复杂工作流3.1 串行执行3.2 并行执行3.3 组合任务系列入口导航Android Jetpack 概述一、概述AndroidWorkManager是 Android Jetpack 组件库的一部分专门用于处理持久性工作。所谓持久性工作是指即便应用退出或设备重启任务也必须执行的情况例如向服务器同步数据或处理本地数据库。它会根据设备的 API 级别和应用状态自动选择最合适的底层调度方式如 JobScheduler、Custom AlarmManager 或 BroadcastReceiver。添加依赖dependencies { implementation androidx.work:work-runtime:2.9.0 }二、核心三大件WorkManager 的基本构成Worker定义任务内容、WorkRequest定义任务如何运行和WorkManager正式调度任务。Worker (工人)这是你编写逻辑的地方。你继承 Worker 类并实现doWork()方法告诉系统“要做什么”。WorkRequest (任务请求)这是你的“订单”。你在这里规定任务是一次性的还是周期性的以及具体的运行规则。WorkManager (管理者)这是系统的指挥官。你把请求交给它它负责在合适的时机调度执行。2.1 定义 Worker 类import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; import androidx.work.Worker; import androidx.work.WorkerParameters; public class MyUploadWorker extends Worker { private static final String TAG MyUploadWorker; public MyUploadWorker(NonNull Context context, NonNull WorkerParameters params) { super(context, params); } NonNull Override public Result doWork() { // 执行实际任务在后台线程运行 try { // 模拟耗时操作 uploadLogs(); return Result.success(); // 任务成功 } catch (Exception e) { Log.e(TAG, 任务失败, e); return Result.failure(); // 任务失败 } // 注意不推荐使用 Result.retry()因为会一直重试 } private void uploadLogs() { // 实际的上传逻辑 Log.d(TAG, 开始上传日志...); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(TAG, 上传完成); } }关键点 doWork() 方法运行在后台线程中所以你可以在这里放心执行耗时操作比如网络请求或数据库写入而不会卡死界面。2.1.1 数据的传递Worker 并不是孤立运行的。你通常需要给它传参数比如图片的 URL同时也可以让它返回结果比如处理后的文件路径。有些任务只需要输入有些只需要输出而有些则两者兼有。输入数据在WorkRequest中通过setInputData(Data)设置。这个再后面详细说获取数据在Worker内部使用getInputData()。输出数据在返回Result.success(outputData)时携带数据。跟Result.success()不一样注意Data对象的大小限制为10KB。它适合传递路径、ID 或简单的标记不适合传递大型位图或数据库条目。2.1.2 任务的结果doWork()必须返回一个 Result这决定了 WorkManager 下一步怎么做结果类型含义Result.success()任务成功完成。Result.failure()任务失败不再重试。Result.retry()任务失败但告诉系统根据“退避政策”Backoff Policy稍后重试。2.1.3 停止与取消如果用户手动取消了任务或者约束条件不再满足比如 Wi-Fi 断了系统会停止你的 Worker。你可以通过isStopped()检查任务是否已被停止。如果你的任务中有循环比如上传大文件你应该在循环中不断检查这个标志位以便优雅地退出。2.2 创建 WorkRequest// 一次性任务 OneTimeWorkRequest uploadWorkRequest new OneTimeWorkRequest.Builder(MyUploadWorker.class) .build(); // 周期性任务最小间隔15分钟 PeriodicWorkRequest periodicWorkRequest new PeriodicWorkRequest.Builder(MyUploadWorker.class, 15, TimeUnit.MINUTES) .build();2.2.1 任务的类型一次性 vs. 周期性根据执行频率WorkRequest分为两个子类OneTimeWorkRequest用于非重复性工作。虽然是“一次性”但如果设置了重试政策它也可能多次运行。PeriodicWorkRequest用于定期运行的任务如每晚备份数据。限制条件最小间隔时间为15 分钟。灵活时段 (Flex Interval)你可以指定任务在间隔期内的最后一段时间内运行。2.2.2 调度配置在构建请求时你可以精确控制它的行为初始延迟 (Initial Delay) 使用 .setInitialDelay(10, TimeUnit.MINUTES)。即便约束条件已满足任务也会等待指定时间后再执行。OneTimeWorkRequest delayedWorkRequest new OneTimeWorkRequest.Builder(MyUploadWorker.class) .setInitialDelay(5, TimeUnit.MINUTES) // 延迟5分钟执行 .build();指数退避策略 (Backoff Policy) 当Worker 返回 Result.retry()时系统需要知道多久后重试。Linear每次重试间隔线性增加。Exponential每次重试间隔指数级增加例如 10s, 20s, 40s...。OneTimeWorkRequest workRequest new OneTimeWorkRequest.Builder(MyUploadWorker.class) .setBackoffCriteria( BackoffPolicy.EXPONENTIAL, // 退避策略指数或线性 10, TimeUnit.SECONDS // 初始延迟时间 ) .build();标记 (Tagging) 通过 .addTag(cleanup) 给任务打标签。这非常有用因为你可以根据标签一次性取消所有相关的任务或者观察它们的状态。// 构建请求 WorkRequest statsRequest new OneTimeWorkRequest.Builder(StatsWorker.class) // 1. 设置初始延迟注册成功 5 分钟后才真正开始跑 .setInitialDelay(5, TimeUnit.MINUTES) // 2. 打上标签方便后续管理 .addTag(user_onboarding_tasks) .addTag(priority_low) .build(); // 提交任务 WorkManager.getInstance(context).enqueue(statsRequest); // --- 稍后在其他地方如退出登录时--- // 3. 通过标签一次性取消所有相关任务 WorkManager.getInstance(context).cancelAllWorkByTag(user_onboarding_tasks);2.2.3 约束条件这是 WorkManager 最强大的地方。你可以定义 Constraints 对象并传给请求Constraints constraints new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) // 需要网络连接 .setRequiresBatteryNotLow(true) // 电量不低 .setRequiresCharging(false) // 不需要充电 .setRequiresDeviceIdle(false) // 设备不需要空闲 .setRequiresStorageNotLow(true) // 存储空间充足 .build(); OneTimeWorkRequest uploadWorkRequest new OneTimeWorkRequest.Builder(MyUploadWorker.class) .setConstraints(constraints) .build();约束项说明setRequiredNetworkType比如CONNECTED有网、UNMETERED仅 Wi-Fi。setRequiresCharging是否必须连接电源。setRequiresDeviceIdle是否在设备空闲时运行适合 CPU 密集型任务。setRequiresBatteryNotLow电量不足时是否停止。2.2.4 输入数据正如我们之前讨论的通过setInputData(Data)传递键值对。Data inputData new Data.Builder() .putString(user_id, 12345) .putInt(file_count, 10) .putStringArray(file_paths, new String[]{/path/1, /path/2}) .build(); OneTimeWorkRequest workRequest new OneTimeWorkRequest.Builder(MyUploadWorker.class) .setInputData(inputData) .build();2.2.5 添加优先级这是 WorkManager 2.7 引入的最接近“高优先级”的机制。它告诉系统这个任务非常重要应该尽快执行不受系统能耗优化如 App Standby Buckets的严格限制。OneTimeWorkRequest highPriorityWork new OneTimeWorkRequest.Builder(MyUploadWorker.class) .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .build();适用场景用户触发的必须立刻看到结果的操作如发送消息、同步重要通知。在setExpedited中OutOfQuotaPolicy枚举主要提供以下两个参数RUN_AS_NON_EXPEDITED_WORK_REQUEST如果加急配额用尽任务不会被取消而是会降级为普通的后台任务。DROP_WORK_REQUEST如果加急配额用尽直接丢弃这个任务请求。2.3 提交任务 WorkManager既然我们已经定义好了“工人”Worker和“合同”WorkRequest现在就到了执行阶段把请求交给WorkManagerWorkManager workManager WorkManager.getInstance(context); workManager.enqueue(uploadWorkRequest);WorkManager 是一个单例你需要通过Context来获取它。如果你不在乎任务是否重复直接使用enqueue方法2.3.1 提交唯一任务有时候你不想重复提交同一个任务比如点击多次“同步”按钮。你可以通过WorkManager.enqueueUniqueWork()来管理唯一任务workManager.enqueueUniqueWork( unique_upload_name, // 任务的唯一标识名称 ExistingWorkPolicy.REPLACE, // 如果任务已存在怎么处理REPLACE, KEEP, APPEND (OneTimeWorkRequest) uploadRequest );KEEP如果已有相同名称的任务在排队则保留旧的忽略新的。REPLACE用新的替换旧的。APPEND将新任务排在旧任务后面形成链。2.3.2 获取任务状态在提交任务后我们通常需要知道任务进行得怎么样了。WorkManager 提供了一套非常强大的机制让你可以通过任务的 id、tag 或 name 来实时监控任务的状态。WorkManager workManager WorkManager.getInstance(context); workManager.getWorkInfoByIdLiveData(workRequest.getId()) .observe(this, workInfo - { if (workInfo ! null) { WorkInfo.State state workInfo.getState(); Data outputData workInfo.getOutputData(); switch (state) { case ENQUEUED: Log.d(TAG, 任务已入队); break; case RUNNING: Log.d(TAG, 任务执行中); break; case SUCCEEDED: Log.d(TAG, 任务成功); break; case FAILED: Log.d(TAG, 任务失败); break; case BLOCKED: Log.d(TAG, 任务被阻塞); break; case CANCELLED: Log.d(TAG, 任务已取消); break; } } });(1) 任务状态 (WorkInfo)当你查询任务状态时你会得到一个 WorkInfo 对象。它包含了任务当前的所有关键信息State (状态)任务是正在排队 (ENQUEUED)、运行中 (RUNNING)、已成功 (SUCCEEDED)、已失败 (FAILED)还是被取消了 (CANCELLED)。Output Data (输出数据)如果任务执行成功并返回了结果你可以从这里拿。Tags (标签)任务关联的标签。Run Attempt Count (运行次数)如果任务失败重试过这里记录了次数。你可以根据提交任务的方式选择不同的获取路径(2) 通过 ID 获取最精确当你提交 WorkRequest 时每个请求都有一个唯一的 UUID。// 假设你保存了请求的 ID UUID workId uploadRequest.getId(); // 使用 LiveData 实时观察 WorkManager.getInstance(context) .getWorkInfoByIdLiveData(workId) .observe(lifecycleOwner, workInfo - { if (workInfo ! null) { System.out.println(当前任务状态: workInfo.getState()); } });(3) 通过 Unique Name 获取最常用如果你使用了enqueueUniqueWork直接用名字查更方便。WorkManager.getInstance(context) .getWorkInfosForUniqueWorkLiveData(sync_logs) .observe(lifecycleOwner, workInfos - { // 注意唯一任务返回的是一个 ListWorkInfo for (WorkInfo info : workInfos) { // 处理状态 } });在使用 getWorkInfosForUniqueWorkLiveData(String uniqueWorkName) 时即便你认为只运行了一个任务WorkManager 依然返回一个 ListWorkInfo。既可以指派给单个任务也可以指派给一整个任务链。(4) 通过 Tag 观察任务状态WorkManager.getInstance(context) // 注意通过 Tag 查询返回的是一个 LiveDataListWorkInfo // 因为一个 Tag 可能对应多个任务请求 .getWorkInfosByTagLiveData(sync_logs) .observe(lifecycleOwner, workInfos - { if (workInfos null || workInfos.isEmpty()) { return; } for (WorkInfo workInfo : workInfos) { WorkInfo.State state workInfo.getState(); System.out.println(任务 ID: workInfo.getId() 状态: state); // 如果你对某个特定状态感兴趣 if (state WorkInfo.State.SUCCEEDED) { // 处理成功后的逻辑 } } });2.3.3 取消任务// 按 ID 取消 workManager.cancelWorkById(workRequest.getId()); // 按标签取消 workManager.cancelAllWorkByTag(upload_tag); // 取消所有任务 workManager.cancelAllWork();及时取消不再需要的任务对节省电量和内存至关重要。不过取消操作并不是“瞬间杀掉”进程它有一些非常关键的底层逻辑当你调用 cancel相关方法时如果任务还在排队ENQUEUED它会直接变成CANCELLED状态永远不会被执行。如果任务正在运行RUNNINGWorkManager 会向你的Worker 发送一个信号。这是最容易被忽视的一点。WorkManager 无法强行停止你的 Java 代码执行除非进程被杀它通过isStopped()标志来通知你。在任务链A - B - C中取消行为具有“连带效应”如果你取消了 任务 A那么依赖它的任务 B 和 任务 C 也会自动被标记为 CANCELLED。这非常智能因为 WorkManager 知道 A 没成功后面的步骤通常已经没有意义了。如果你使用的是 enqueueUniqueWork除了手动调用 cancelUniqueWork(name)你还可以通过提交一个同名但策略为 REPLACE的新任务来隐式地取消并替换掉旧任务。取消方式适用场景cancelWorkById精确控制比如用户点击了某个特定文件的“取消上传”按钮。cancelAllWorkByTag批量操作比如关闭了某个功能模块需要停掉该模块下所有的后台任务。cancelUniqueWork业务逻辑控制确保某个命名的业务流程彻底停止。三、任务链和复杂工作流它能让你把多个简单的 Worker像乐高积木一样组合成复杂的业务流。3.1 串行执行最简单的形式是做完 A再做 B最后做 C。WorkManager workManager WorkManager.getInstance(context); OneTimeWorkRequest workA new OneTimeWorkRequest.Builder(WorkerA.class).build(); OneTimeWorkRequest workB new OneTimeWorkRequest.Builder(WorkerB.class).build(); OneTimeWorkRequest workC new OneTimeWorkRequest.Builder(WorkerC.class).build(); // A - B - C 顺序执行 workManager.beginWith(workA) .then(workB) .then(workC) .enqueue();特点如果workA 失败整个链条会停止B 和 C 都不会运行。3.2 并行执行// 同时执行 A 和 B然后执行 C workManager.beginWith(workA, workB) .then(workC) .enqueue();在这个场景中workA 和 workB 会被同时放入调度队列。只有当它们两个都成功完成返回 Result.success()时workC 才会触发执行。当 workA 和 workB 并行运行并分别返回数据时workC 接收到的输入数据Input Data会是两者的并集。如果 workA 返回 {key1: value1}如果 workB 返回 {key2: value2}那么 workC 通过 getInputData() 就能同时拿到 key1 和 key2。3.3 组合任务除了简单的并行你还可以通过WorkContinuation.combine()把多个已经存在的任务链合并在一起。OneTimeWorkRequest workA, workB, workC, workD; // A 和 B 并行完成后执行 CD 等待 A 完成 WorkContinuation continuation1 workManager.beginWith(workA, workB); WorkContinuation continuation2 workManager.beginWith(workC); WorkContinuation continuation3 workManager.beginWith(workD); WorkContinuation combined WorkContinuation .combine(continuation1, continuation2, continuation3) .then(workE); combined.enqueue();