当前位置: 首页 > news >正文

Android Jetpack 系列(六)WorkManager 任务调度实战详解

1. 简介

在现代 Android 开发中,如果你需要处理如:上传日志、备份照片、定期同步、数据库清洗等重要但不要求实时完成的后台任务,就不能忽视系统对后台行为的严格限制:

Android 系统从 Doze 模式开始,陆续引入了多项电量优化策略,如:

  1. Doze / App Standby / JobScheduler 限制
  2. 后台 Service 限制(Android 8 开始)
  3. 省电模式 / 网络调度限制 / 定时任务最小间隔

为确保后台任务仍能可靠执行,Jetpack 推出了统一的解决方案 —— WorkManager。

WorkManager 是 Jetpack 提供的用于处理可延期且需要保证执行的后台任务的库。

核心价值:

  1. 能自动适配 Android 各版本下的调度机制
  2. 支持任务链、条件约束、任务重试、任务持久化
  3. 保证任务持久化,即:在 App 重启、设备重启后仍能执行
  4. 最低兼容 API 14,覆盖主流 Android 设备

需要注意:

  1. WorkManager 并不是线程池的替代品,它并不适用于“立即响应”的任务。
  2. 如果你想在特定时间精准触发任务 → 推荐用 AlarmManager;
  3. 如果你需要立刻前台执行任务 → 推荐用 ForegroundService。
  4. WorkManager 适合的是:可以延迟,但必须完成 的任务。

2. 了解 WorkManager 的特性和类型

与其他方案对比:

特性

WorkManager

JobScheduler

AlarmManager

ForegroundService

最低支持版本

API 14

API 21

API 1

API 1

是否持久化任务

✔ 是

✔ 是

❌ 否

❌ 否

支持约束条件

精准执行时间

❌ 否

❌ 否

✔ 是

✔ 是

可否后台执行

任务可靠性

⭐⭐⭐⭐⭐

⭐⭐⭐⭐

⭐⭐

WorkManager 可处理的三种类型的持久性工作:

立即执行:必须立即开始且很快就完成的任务,可以设置 expedited 加急执行。

长时间运行:运行时间可能较长(有可能超过 10 分钟)的任务。

可延期执行:延期开始并且可以周期性(最小周期15分钟)运行的预定任务。

不同类型的持久性工作之间的关系如下图:

下表大致列出了各种工作类型。

类型

周期性

访问方式

即时

一次

OneTimeWorkRequest 和 Worker。如需处理加急工作,请对 OneTimeWorkRequest 调用 setExpedited()

长时间运行

一次性或定期

任意 WorkRequest 或 Worker。在工作器中调用 setForeground() 来处理通知。

可延期

一次性或定期

PeriodicWorkRequestWorker

3. 添加依赖

要在项目中使用 WorkManager,需要在模块的 build.gradle.kts 或 build.gradle 文件中添加如下依赖项:

dependencies {val work_version = "2.10.3"// Kotlin 扩展与协程支持implementation("androidx.work:work-runtime-ktx:$work_version")// 可选 Test helpersandroidTestImplementation("androidx.work:work-testing:$work_version")
}

4. 开始使用WorkerManager

4.1. 定义工作单元

4.1.1 继承 Work

工作使用 Worker 类定义。其doWork() 方法在 WorkManager 提供的后台线程上异步运行。

例如,如需创建上传的 Worker,可以执行以下操作:

class UploadWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {override fun doWork(): Result {return try {// 模拟线程在阻塞操作Thread.sleep(5000)Result.success()} catch (e: Exception) {e.printStackTrace()Result.failure()}}
}

说明:

  • doWork() 方法运行在WorkManager内开启的一个后台线程,要求同步返回Result,不支持挂起函数。
  • 如果你在 doWork() 中使用了挂起函数(比如 withContext),你需要用 runBlocking 包裹 —— 否则无法执行协程。
  • doWork() 方法返回的 Result 会通知 WorkManager 服务工作是否成功,以及工作失败时是否应重试工作。Result值有:

                Result.success():工作成功完成。

                Result.failure():工作失败。

                Result.retry():工作失败,会根据其定义的重试政策在其他时间尝试。

4.1.2 继承 CoroutineWorker

如果你的项目是 Kotlin + 协程架构(如 ViewModel + Flow + Retrofit 等),优先考虑使用 CoroutineWorker 对 Worker进行替换,因为它更现代、易读、易维护。

修改上述示例:

class UploadWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {override suspend fun doWork(): Result {return try {// 模拟线程在阻塞操作delay(5000)Result.success()} catch (e: Exception) {e.printStackTrace()Result.failure()}}
}

说明:

  • doWork() 方法也运行在WorkManager内开启的一个后台线程,doWork() 是一个 suspend 函数,天然支持 Kotlin 协程,可优雅地处理异步逻辑。
  • 更适合现代 Android 项目,配合协程的挂起函数使用(比如 Retrofit、Room、DataStore 等)。
  • 推荐在大多数 Kotlin 项目中使用它代替传统 Worker。

4.2. 定义工作请求(WorkRequest)

Worker 定义工作单元,而 WorkRequest(及其子类)则定义工作运行方式和时间。WorkRequest 对象包含 WorkManager 调度和运行工作所需的所有信息。其中包括运行工作必须满足的约束、调度信息(例如延迟或重复间隔)、重试配置,并且可能包含输入数据(如果工作需要)。

WorkRequest 本身是抽象基类。该类有两个派生实现:

  • OneTimeWorkRequest 适用于调度非重复性工作,
  • PeriodicWorkRequest 则更适合调度以一定间隔重复执行的工作。

4.2.1. 调度一次性工作(OneTimeWorkRequest)

// 无需额外配置的基本工作,请使用静态方法 from
val uploadWorkRequest: WorkRequest = OneTimeWorkRequest.from(UploadWorker::class.java)
// 更复杂的工作,可以使用构建器
val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().build()

4.2.2. 调度定期工作(PeriodicWorkRequestBuilder)

应用有些任务不是一次性的,而是需要反复在后台操作,比如:每天备份一次数据、每小时检查一次新内容、每隔一段时间上传一次日志等。

可以通过 PeriodicWorkRequest 来让工作自动按间隔重复执行。

// 间隔1小时执行一次工作
val uploadWorkRequest: WorkRequest = PeriodicWorkRequestBuilder<UploadWorker>(1, TimeUnit.HOURS).build()

注意:

  • repeatInterval 参数是设置工作两次执行之间的最短时间间隔
  • 实际执行时间可能会有延迟,因为系统会根据:设置的 约束条件(Constraints)、系统的 电量优化策略,进行调整
  • 最短间隔是 15 分钟(跟 JobScheduler 一样),小于这个会报错。

灵活时间段(flexInterval)

有时候工作不必在间隔开始的第一秒运行,比如:“我只需要在每小时的最后 15 分钟内运行一次就好。”

这时可以用 灵活间隔(flexInterval)来定义一个“可执行时间窗口”。

  • repeatInterval:整个周期的长度
  • flexInterval:周期结束前的一段时间,WorkManager 会尽量在这段时间执行工作

每小时的最后 15 分钟执行示例:

val uploadWorkRequest = PeriodicWorkRequestBuilder<UploadWorker>(1, TimeUnit.HOURS,     // repeatInterval:1小时15, TimeUnit.MINUTES   // flexInterval:最后15分钟
).build()

限制:

  • repeatInterval ≥ PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS(15 分钟)
  • flexInterval ≥ PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS(5 分钟)

4.2.3. 设置加急工作(Expedited)

从 WorkManager 2.7.0 开始,Google 引入了“加急工作(expedited work)”特性,用于执行那些非常重要且需要立即运行的任务,比如:

  1. 上传日志、同步数据
  2. 用户点击“发送消息”或“确认付款”等动作

相比普通的 WorkManager 任务,加急工作优先级更高,可以绕过 Doze 模式和省电策略,前提是系统允许,且配额充足。

加急工作的定义

val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST).build()

其中 OutOfQuotaPolicy 有两个选项:

  • RUN_AS_NON_EXPEDITED_WORK_REQUEST:表示配额不足时降级为普通任务(推荐);
  • DROP_WORK_REQUEST:表示配额不足时直接丢弃任务。

加急工作的五个核心特点:

  • 用户优先
    通常是由用户行为触发的任务(如点击按钮),需要立刻执行,不可排队。
  • 执行快、耗时短
    适合快速完成的短任务(几分钟内),不适合长时间运行任务(如视频转码)。
  • 有执行配额限制
    每个 App 每天有一定“加急时间”,消耗完之后会受限。系统不会告知配额剩余量。
  • 可以绕过系统限制
    支持在 Doze 模式、省电模式下运行,类似临时“提权”运行。
  • 必须立即尝试执行
    加急任务不等待、立即调度(前提是系统资源允许和配额充足)

配额说明

  • 没有 API 获取当前配额,你无法得知配额剩多少、多久刷新;
  • 系统会根据行为、状态动态调整配额;
  • App 在前台运行时使用加急任务不会消耗配额
  • 日志中若看到:Expedited job was denied due to quota 就说明配额已耗尽。

开启日志调试方式需要自定义初始化,在下面章节会详细说明:

WorkManager.initialize(context, Configuration.Builder().setMinimumLoggingLevel(Log.DEBUG).build())

Android 版本兼容性

Android 版本

加急工作行为

Android 12+

要求必须手动调用 setForeground() / setForegroundAsync() 启动前台服务,否则任务会失败并抛出 IllegalStateException 异常

Android 12 以下

系统没有加急概念,WorkManager 会自动将加急任务转换为前台服务运行

总结:不管系统版本如何,加急任务都必须显示通知、运行为前台服务。

然而当 WorkManager 想用前台服务运行任务时,它必须告诉系统:

  1. 这个任务要显示什么通知?
  2. 要怎么构造前台服务?

也就是在定义Wroker 时,需要额外实现 getForegroundInfo / getForegroundInfoAsync 方法,否则调用 setExpedited() 后会在旧系统上导致崩溃。

前台服务相关方法

方法名

使用对象

getForegroundInfo()

CoroutineWorker 或 Worker

getForegroundInfoAsync()

ListenableWorker(CoroutineWorker 和 Worker 的基类)

setForeground()

CoroutineWorker

setForegroundAsync()

Worker 或 ListenableWorker

Worker 示例代码

继承Worker:

class UploadWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {override fun doWork(): Result {setForegroundAsync(getForegroundInfo())return try {// 模拟线程在阻塞操作Thread.sleep(5000)Result.success()} catch (e: Exception) {e.printStackTrace()Result.failure()}}override fun getForegroundInfo(): ForegroundInfo {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW)val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManagermanager.createNotificationChannel(channel)}val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID).setContentTitle("正在上传").setSmallIcon(R.drawable.ic_upload).build()// Android 14+ 还需要指定前台服务类型return ForegroundInfo(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)}
}

继承 CoroutineWorker:

class UploadWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {override suspend fun doWork(): Result {setForeground(getForegroundInfo())return try {// 模拟线程在阻塞操作delay(5000)Result.success()} catch (e: Exception) {e.printStackTrace()Result.failure()}}override suspend fun getForegroundInfo(): ForegroundInfo {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW)val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManagermanager.createNotificationChannel(channel)}val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID).setContentTitle("正在上传").setSmallIcon(R.drawable.ic_upload).build()// Android 14+ 还需要指定前台服务类型return ForegroundInfo(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)}
}

注意事项:

  • 加急工作仅支持一次性任务 OneTimeWorkRequest,不支持周期性任务 PeriodicWorkRequest。
  • 加急工作会启动前台服务,所以还要进行android.permission.POST_NOTIFICATIONS 权限 和 FOREGROUND_SERVICE 权限的声明。如果是Android14及以上还需要对 Service指定前台服务类型: android:foregroundServiceType。
  • 只要你的 App 还在前台,加急任务会立刻被允许执行,但不会被系统限制配额。但如果用户按了 Home 或锁屏,那配额限制就会生效。

4.2.4. 设置工作约束(Constraints)

工作约束用于将工作延迟到满足最佳条件时运行。例如,仅在设备使用Wi-Fi网络连接时、当设备处于空闲状态或者有足够的电量时运行。以下约束适用于 WorkManager:

约束

说明

NetworkType

约束运行工作所需的网络类型。值有:不需要网络(NOT_REQUIRED)、需要有网络(CONNECTED)、Wi-Fi (UNMETERED)、非漫游网络(NOT_ROAMING)、计费移动网络(METERED)。

BatteryNotLow

如果设置为 true,表示当设备有足够电量时才会运行工作。

Charging

如果设置为 true,表示当设备充电时才运行工作。

DeviceIdle

如果设置为 true,表示设备必须处于空闲状态,才能运行工作。在运行批量操作时,若是不用此约束,批量操作可能会降低设备上正在积极运行的其他应用的性能。

StorageNotLow

如果设置为 true,表示设备有足够的存储空间才运行工作。

如需创建一组约束并将其与某项工作相关联,请使用一个 Constraints.Builder() 创建 Constraints 实例。

例如,以下代码会构建一个工作请求,该请求仅在用户设备正在充电且连接到 Wi-Fi 网络时才会运行:

val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).setRequiresCharging(true).build()val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().setConstraints(constraints).build()

说明:

  • 如果指定了多个约束,工作必须满足所有约束时才会运行。
  • 如果在工作运行时不满足某个约束,WorkManager 会停止工作器。系统会在满足所有约束后重试工作。

4.2.5. 设置延迟工作(InitialDelay)

如果一个任务没有设置任何约束,或者在任务加入队列时其所有约束条件都已满足,系统可能会立即执行该任务。
如果你希望任务不是马上执行,而是在加入队列后等待一段时间再开始,可以通过初始延迟(Initial Delay) 来实现。

例如,下面的示例将任务设置为在加入队列 至少 10 分钟后 才执行:

val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().setInitialDelay(10, TimeUnit.MINUTES).build()

说明:

  • 对于 OneTimeWorkRequest:初始延迟会直接影响任务的开始时间。
  • 对于 PeriodicWorkRequest:初始延迟 只会影响首次执行时间,后续则按周期运行。

注意:

任务的实际执行时间不仅取决于你设置的延迟,还会受到 WorkRequest 中其他约束条件(如网络、电量等)以及系统调度优化的影响。

4.2.6. 设置重试与退避策略(BackoffCriteria)

如果任务执行失败,并且你希望它 稍后自动重试,可以在 Worker 的 doWork() 方法中返回:

return Result.retry()

这样,WorkManager 就会根据 退避延迟时间退避策略 来重新安排任务的执行。

退避延迟时间(Backoff Delay):

  1. 定义:第一次失败后,到再次重试之间的最短等待时间。
  2. 限制:不能小于 10 秒(MIN_BACKOFF_MILLIS),默认是 30 秒。

退避策略(Backoff Policy):

退避策略决定了每次失败后,等待时间是如何增长的。
WorkManager 提供两种策略:

策略

特点

等待时间示例(初始值 10 秒)

LINEAR(线性)

每次重试间隔按固定时间递增

10 秒 → 20 秒 → 30 秒 → 40 秒...

EXPONENTIAL(指数)

每次重试间隔按倍数递增

10 秒 → 20 秒 → 40 秒 → 80 秒...

例如,下面的代码将退避策略设为 LINEAR,并将初始退避延迟设为 允许的最小值(10 秒)

val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().setBackoffCriteria(BackoffPolicy.LINEAR, WorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS).build()

说明:

运行逻辑示例(假设任务一直失败并返回 Result.retry()):

第一次失败 → 10 秒后 重试

第二次失败 → 20 秒后 重试

第三次失败 → 30 秒 重试
...以此类推,如果改成 EXPONENTIAL,等待时间则会接近:10 秒 → 20 秒 → 40 秒 → 80 秒 → ...

注意:

退避延迟不是精确计时,可能有几秒误差,但绝不会低于你配置的初始值。

4.2.7. 设置/添加标记工作(Tag)

每个 WorkRequest 都有一个唯一的 ID,用于后续 取消任务 或 查询进度。
如果有多条任务在逻辑上相关,我们还可以为它们加上 标签(Tag),方便批量管理。

为什么要用标签?

  • 可以一次性取消同标签的所有任务:
  • WorkManager.cancelAllWorkByTag("uploadImage ")
  • 可以一次性查询同标签任务的状态:
  • val workInfos = WorkManager.getWorkInfosByTag("uploadImage ")

例如给任务添加标签:

val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().addTag("uploadImage").build()

注意:

  • 可以向单个工作请求添加多个标记。这些标记在内部以一组字符串的形式进行存储。
  • 可以使用 WorkInfo.getTags() 获取与 WorkRequest 关联的标记集。
  • 从 Worker 类中,可以使用 ListenableWorker.getTags() 检索其标记集。

4.2.8. 设置输入数据(InputData)

有些任务在执行时需要外部数据,例如:上传图片任务需要 图片 URI、备份任务需要 文件路径。WorkManager 通过 Data 类以 键值对 形式传递输入数据。

在 WorkRequest 中传递输入数据:

val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().setInputData(workDataOf("IMAGE_URI" to "http://example.com/image.jpg")).build()

定义需要接收数据的 Worker:

class UploadWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {override fun doWork(): Result {val imageUri = inputData.getString("IMAGE_URI")?: return Result.failure()return try {uploadFile(imageUri)return Result.success()} catch (e: Exception) {e.printStackTrace()Result.failure()}}
}

如果需要在任务成功或失败的结果输出更多的信息,可以修改return Result.success()和 Result.failure:

return try {uploadFile(imageUri) return Result.success(workDataOf("code" to "200"))
} catch (e: Exception) {e.printStackTrace()Result.failure(workDataOf("code" to "1001"))
}

4.3. 管理工作

当你已经定义好了一个 Worker(任务执行逻辑)和一个 WorkRequest(任务调度计划),最后一步就是把它交给 WorkManager 让它去执行。
最简单的做法是用:

WorkManager.getInstance(context).enqueue(uploadWorkRequest)

这行代码就能把任务加到队列里,等条件满足就执行。但是有一点需要特别注意:
如果你多次调用 enqueue(),就会多次把同样的任务加到队列中(比如你的 App 每 24 小时上传一次日志,但用户重复进入触发页面,你就可能一天上传好几次)。
解决办法就是用 唯一工作(Unique Work),它能保证同一时刻只有一个同名任务在队列中。

4.3.1. 唯一工作

唯一工作就是可确保同一时刻只有一个具有特定名称的工作实例。它的名称由开发者自己定义的、可读性强的名字(不是系统自动生成的 UUID)。它既适用于一次性任务(enqueueUniqueWork())也适用于定期任务(enqueueUniquePeriodicWork())。

方法和参数

可以通过调用以下方法之一创建唯一工作:

// 一次性任务
WorkManager.getInstance(context).enqueueUniqueWork()// 定期任务
WorkManager.getInstance(context).enqueueUniquePeriodicWork()

它们都需要 3 个参数:

  • uniqueWorkName: 唯一任务名
  • existingWorkPolicy: 冲突策略(已有任务未完成时,怎么处理新任务)
  • work: 要调度的 WorkRequest

冲突策略

一次性任务支持 4 种策略:

  • REPLACE → 取消现有任务,用新的替换。
  • KEEP → 保留现有任务,忽略新任务。
  • APPEND → 把新任务接到旧任务后面,按顺序执行(旧任务失败或取消,新任务也会失败或取消)。
  • APPEND_OR_REPLACE → 如果旧任务还在运行,把新任务追加到旧链的末尾;如果旧任务已经结束/失败/取消,则直接替换旧链,重新启动新的链。

定期任务支持 3 种策略:

  • REPLACE → 取消现有任务,用新的替换(已废弃,使用 UPDATE 代替)。
  • KEEP → 保留现有任务,忽略新任务。
  • UPDATE → 更新任务。
  • CANCEL_AND_REENQUEUE  → 先取消,待新的条件满足后再重新入队。

示例

假设要每天上传一次日志,而且必须连上Wifi和充电时才能传:

val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).setRequiresCharging(true).build()val uploadLogWorkRequest = PeriodicWorkRequestBuilder<UploadWorker>(24, TimeUnit.HOURS).setConstraints(constraints).addTag("upload").build()WorkManager.getInstance(context).enqueueUniquePeriodicWork("UploadLog", ExistingPeriodicWorkPolicy.KEEP, uploadLogWorkRequest)

4.3.2. 观察工作

在将任务加入队列后,你可以随时按 WorkRequest  的id、tags 或唯一任务名在 WorkManager 中进行查询,以检查其状态(关于工作状态会在下面章节介绍)。

方法名

Flow版本

LiveData版本

getWorkInfoById()

getWorkInfoByIdFlow()

getWorkInfoByIdLiveData()

getWorkInfosByTag()

getWorkInfosByTagFlow()

getWorkInfosByTagLiveData()

getWorkInfosForUniqueWork()

getWorkInfosForUniqueWorkFlow()

getWorkInfosForUniqueWorkLiveData()

示例代码

WorkManager.getInstance(context).getWorkInfoById(uploadLogWorkRequest.id)
WorkManager.getInstance(context).getWorkInfosByTag("upload")
WorkManager.getInstance(context).getWorkInfosForUniqueWork("UploadLog")

可以用 LiveData 或 Flow 版本来监听任务状态变化,例如任务成功后弹出提示:

WorkManager.getInstance(context).getWorkInfoByIdLiveData(uploadLogWorkRequest.id).observe(viewLifecycleOwner) { workInfo ->if (workInfo?.state == WorkInfo.State.SUCCEEDED) {Snackbar.make(requireView(), R.string.work_completed, Snackbar.LENGTH_SHORT).show()}}WorkManager.getInstance(context).getWorkInfoByIdFlow(uploadLogWorkRequest.id).collect { workInfo ->if (workInfo?.state == WorkInfo.State.SUCCEEDED) {Snackbar.make(requireView(), R.string.work_completed, Snackbar.LENGTH_SHORT).show()}
}

观察工作进度

有时候后台任务不是一瞬间完成的,比如上传文件、同步数据、批量上传图片。这类任务最好能在界面上看到“进度”,比如 0%、50%、100%,让用户心里有数。

WorkManager 提供了两个 API 用来在 Worker 内部更新进度:

方法名

使用对象

setProgress()

CoroutineWorker

setProgressAsync()

Worker 或 ListenableWorker

Worker 示例代码

class UploadWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {companion object {const val PROGRESS = "Progress"}override suspend fun doWork(): Result {return try {// 第一次更新:0%// Java 语言且是继承 Worker 的,请用 setProgressAsync(new Data.Builder().putInt(PROGRESS, 0).build());setProgress(workDataOf(PROGRESS to 0))// 模拟耗时delay(1000)// 第二次更新:50%setProgress(workDataOf(PROGRESS to 50))// 模拟耗时delay(1000)// 第三次更新:100%setProgress(workDataOf(PROGRESS to 100))Result.success()} catch (e: Exception) {e.printStackTrace()Result.failure()}}
}

UI 层可通过WorkRequest ID 监听这个 Worker 的状态和进度。

WorkManager.getInstance(context).getWorkInfoByIdFlow(uploadLogWorkRequest.id).collect { workInfo ->if (workInfo != null) {val progress = workInfo.progress.getInt(UploadWorker.PROGRESS, 0)progressBar.progress = progress // UI 更新}
}

复杂的查询

从 WorkManager 2.4.0开始,新增了 WorkQuery 对象,它可以用来对已加入队列的任务进行复杂的查询。通过 WorkQuery 可以根据多个条件(如:标签、状态和唯一工作名称)来筛选作业。

假设我们需要查询所有具有 "syncTag" 标签、处于 FAILED 或 CANCELLED 状态,且唯一名称为 "preProcess" 或 "sync" 的任务。

val workQuery = WorkQuery.Builder.fromTags(listOf("syncTag")).addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED)).addUniqueWorkNames(listOf("preProcess", "sync")).build()
val workInfos: ListenableFuture<List<WorkInfo>> = WorkManager.getInstance(context).getWorkInfos(workQuery)

说明:

  1. 标签、状态和唯一任务名称之间使用 AND 关系。例如,查询结果必须同时满足标签、状态和任务名称的条件。
  2. 每个条件中的多个值之间使用 OR 关系。例如,状态可以是 FAILED 或 CANCELLED,任务名称可以是 preProcess 或 sync。
  3. getWorkInfos 方法同样提供了LiveData 和 Flow 版本。

4.3.3. 取消工作

你可以按 ID、唯一名称或 Tag 取消任务:

WorkManager.getInstance(context).cancelWorkById(uploadLogWorkRequest.id)
WorkManager.getInstance(context).cancelAllWorkByTag("upload")
WorkManager.getInstance(context).cancelUniqueWork("UploadLog")

被取消的任务会变成 CANCELLED 状态,依赖它的任务也会被取消。

4.3.4. 停止工作的原因

正在运行的任务可能会由于以下几种原因而停止运行:

  • 明确要求取消它(例如,通过调用 WorkManager.cancelWorkById(UUID) 取消)。
  • 如果是唯一工作,使用 ExistingWorkPolicy.REPLACE 替换现有任务。
  • 工作约束条件已不再满足(比如断网)。
  • 系统强制停止(如超过 10 分钟的执行期限),若存在此情况,任务会调度为在稍后重试。

onStopped() 回调

Worker 停止时,WorkManager 会调用 ListenableWorker.onStopped()。我们可以在定义 Work 中重写 onStopped() 方法进行释放资源。

isStopped() 属性

isStopped()是 Worker 运行时的一个标志,告诉工作中的任务是否被 WorkManager 停止。

为什么需要它?因为有些任务不是一条语句就能完成的,而是一个循环、分步执行的长任务。

如果你在这些过程中没有检查 isStopped(),那即使用户或系统取消了任务,你的循环还是会继续跑,造成资源浪费。

class UploadWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {override fun doWork(): Result {try {val photoList = getPhotoList()for (photo in photoList) {// 每次循环检查是否被停止if (isStopped) {// 主动中止,避免浪费资源return Result.failure()}uploadPhoto(photo)}return Result.success()} catch (e: Exception) {e.printStackTrace()return Result.failure()}}override fun onStopped() {super.onStopped()// 释放资源(如关闭网络连接)closeUploadSession()}
}

如需调试 Worker 停止的原因,可以调用 WorkInfo.getStopReason() 来记录停止原因:

WorkManager.getInstance(context).getWorkInfoByIdFlow(uploadLogWorkRequest.id).collect { workInfo ->if (workInfo != null) {val stopReason = workInfo.stopReason}}

4.4. 工作状态

4.4.1. 什么是“工作状态”

在 WorkManager 中,每一个工作(WorkRequest)都像一个有生命的任务,从创建到结束会经历一系列状态变化。这些状态由 WorkInfo.State 枚举表示,比如:

  • ENQUEUED:排队等待执行
  • RUNNING:正在执行
  • SUCCEEDED:执行成功
  • FAILED:执行失败
  • CANCELLED:被取消
  • BLOCKED:被阻塞(等待前面的任务完成)
  • ENQUEUED(再次进入):某些情况下会回到排队状态

4.4.2. 一次性工作的生命周期

对于 one-time 工作请求,流程如下:

  1. 初始状态 → ENQUEUED
    当你调用 WorkManager.enqueue() 后,任务进入等待队列。
    它会等到:
          · 所有 约束条件(Constraints) 满足(如网络可用、设备充电中等)
          · 任何设置的延迟时间到达
  2. 运行 → RUNNING
          · 条件满足后,任务开始执行。
  3. 执行结果
          · ​​​​ 成功 → 进入 SUCCEEDED(终止状态)
          · ​​​​​​ 失败 → 进入 FAILED(终止状态)
          ·  需要重试 → 回到 ENQUEUED 状态,等待下次执行机会
  4. 随时可取消
          · 不管处于什么状态(除了终止状态),你都可以取消它,任务会变为 CANCELLED(终止状态)。

下图展示了一次性工作的生命周期:

注意:

终止状态:SUCCEEDEDFAILEDCANCELLED 都意味着任务生命周期结束,此时 WorkInfo.State.isFinished() 会返回 true。

4.4.3. 定期工作的生命周期

Periodic Work就像一个永远不会自己结束的闹钟:

  1. 它会周期性地进入 RUNNING 状态执行任务。
  2. 每次执行后,不管结果是成功、失败还是需要重试,都会重新被调度到下一个周期。
  3. 它唯一的终止方式是被取消 → CANCELLED

下图展示了定期工作的生命周期:

注意:

定期任务没有 SUCCEEDEDFAILED 状态。

4.4.4. BLOCKED 状态

该状态专门出现在工作链(Chained Work)里:

  1. 如果你让任务 B 依赖任务 A 的执行结果,那么当 A 还没完成时,B 会处于 BLOCKED 状态。
  2. 一旦 A 成功(或失败,但你允许继续),B 才能变成 ENQUEUED 进入正常流程。

4.5.工作链

在实际开发里,我们经常需要 多个任务按顺序执行,比如:下载数据、缓存到本地、上传到服务器。这种情况就需要用工作链(Work Chaining)

在 WorkManager 里,你可以:

  1. beginWith(...) 定义起始任务(可以是一个,也可以是多个并行任务)
  2. then(...) 接上后续任务(这些会依赖前面所有的任务完成后才能运行)
  3. 最后用 enqueue() 启动整个链条

下面我们来看一个示例。 比如我们有3个并行的任务需要先完成,接着先做好缓存,最后是上传到远程服务器:

WorkManager.getInstance(context)// 先执行 plantName1、plantName2、plantName3(可能并行).beginWith(listOf(plantName1, plantName2, plantName3))// 这些都完成后,再执行 cache.then(cache)// cache 完成后,再执行 upload.then(upload)// 最后开始执行整个链.enqueue()

4.5.1. 链接任务间传递数据(InputMerger)

任务之间可以传数据:前一个任务的输出 → 下一个任务的输入。
但如果前面是多个任务并行,怎么把它们的输出合并?这就需要 InputMerger。

WorkManager 提供两种常见合并器:

  1. OverwritingInputMerger(默认)

如果键相同,后写入的值会覆盖前面的值,举例:

  1. plant1 输出:{"plantName"="tulip"}
  2. plant2 输出:{"plantName"="elm"}
  3. cache 收到的可能是 "tulip" 或 "elm",取决于最后写入的那个
  1. ArrayCreatingInputMerger

如果键相同,会把所有值放到一个数组里,举例:

  1. plant1 输出:{"plantName"="tulip"}
  2. plant2 输出:{"plantName"="elm"}
  3. cache 收到的是 {"plantName"=["tulip","elm"]}

上述示例,在定义 cache 工作请求时,应该使用 setInputMerger 方法来指定数据传递 方式:

val cache: OneTimeWorkRequest = OneTimeWorkRequestBuilder<PlantWorker>().setInputMerger(ArrayCreatingInputMerger::class.java).setConstraints(constraints).build()

4.5.2. 链接工作和状态变化

默认执行逻辑

只要前一个任务成功(Result.success()),下一个任务才会启动。
失败或取消会传递下去。

BLOCKED 状态

在链里,后续任务如果前面的任务还没完成,就会进入 BLOCKED 状态,等前面完成后才会进入队列。

失败重试
如果任务失败,可以按你设置的退避策略(Backoff Policy)重试。

  • 比如 taskB 失败,taskC、taskD 会被阻塞,直到 B 重试成功。

  • 如果 B 永远失败,整个链条会被标记为 FAILED。

取消逻辑
如果你取消了链条中的一个任务,那么它后面的所有依赖任务也会变成 CANCELLED。

扩展链条
如果链中的某个任务已经失败/取消,你再往后追加新的任务,那么新任务也会直接被标记为失败或取消。

4.5.3 扩展或追加链条(ExistingWorkPolicy.APPEND_OR_REPLACE)

前面我们提到,如果链条里的某个任务失败或被取消,那么后续依赖任务也会跟着失败/取消。

这就带来一个新问题:如果我想在已经存在的链条后面追加新的任务,该怎么办?

在前面【4.3.1. 唯一工作】中提到,可以使用唯一工作(UniqueWork)来管理链条,并通过 ExistingWorkPolicy. APPEND_OR_REPLACE 来扩展现有链。

假设上面的示例中,我们突然希望在 upload 之后,再追加一个 埋点上报任务(analytics)。

问题:

原有链条已经 enqueue() 启动,无法再直接用 .then(analytics)。
如果强行加,新任务可能直接变成 FAILED 或 CANCELLED(因为前序依赖的 upload 已经完成/失败/取消)。

解决方法:

这时就要用 唯一工作(UniqueWork) + ExistingWorkPolicy.APPEND_OR_REPLACE。

示例代码:

// 修改原始链:plantName1, plantName2, plantName3 → cache → upload
WorkManager.getInstance(context).beginUniqueWork("plant_chain",ExistingWorkPolicy.KEEP, // 第一次创建时随便用 KEEPlistOf(plantName1, plantName2, plantName3)).then(cache).then(upload).enqueue()// 稍后要在链条后追加 analytics
val analytics = OneTimeWorkRequestBuilder<AnalyticsWorker>().build()WorkManager.getInstance(context).beginUniqueWork("plant_chain", // 使用同一个唯一链的名字ExistingWorkPolicy.APPEND_OR_REPLACE,analytics).enqueue()

运行逻辑:

  1. 如果 "plant_chain" 还在运行中:analytics 会被追加到链尾部,变成:plantName1、plantName2、plantName3 → cache → upload → analytics
  2. 如果链条已经结束/失败/取消:系统会直接新开一条链,只运行:analytics

4.6. 长时间运行任务

正常情况下,WorkManager 的任务运行时间是有限制的(受 JobScheduler 限制,大约 10 分钟左右)。但有些任务确实可能要跑很久,比如:批量上传/下载一个超大的文件;用户发起导出、备份的重要操作等。那么问题来了:如果任务超过 10 分钟怎么办?

WorkManager 会帮你在后台启动一个前台服务 (Foreground Service)。这就意味着:

  1. 系统会尽量保证进程活着(不被杀掉)
  2. 任务可以一直跑下去(超过 10 分钟也没问题)
  3. 用户能在通知栏看到进度,并且有取消的入口

WorkManager 提供了两个 API 用来声明需要前台服务:

方法名

使用对象

setForeground()

CoroutineWorker

setForegroundAsync()

Worker 或 ListenableWorker

setForegroundsetForegroundAsync 我们在前面【4.2.3. 设置加急工作】已经有过介绍,不同的是,长时间运行任务并不需要额外实现 getForegroundInfo / getForegroundInfoAsync 方法。

Worker 示例代码

class UploadWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {override suspend fun doWork(): Result {setForeground(createForegroundInfo("开始上传..."))return try {// 模拟线程在阻塞操作delay(5000)setForeground(createForegroundInfo("上传完成!"))Result.success()} catch (e: Exception) {e.printStackTrace()Result.failure()}}private fun createForegroundInfo(progress: String): ForegroundInfo {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW)val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManagermanager.createNotificationChannel(channel)}// 用户可点通知里的“取消”val cancelIntent = WorkManager.getInstance(applicationContext).createCancelPendingIntent(id)val notification = NotificationCompat.Builder(applicationContext, "download_channel").setContentTitle("文件上传").setContentText(progress).setSmallIcon(R.drawable.ic_launcher_foreground).setOngoing(true).addAction(android.R.drawable.ic_delete, "取消", cancelIntent).build()// Android 14+ 还需要指定前台服务类型return ForegroundInfo(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)}
}

注意事项

  • JobScheduler 依然在背后调度:即使 WorkManager 启动了前台服务,它本质上还是 JobScheduler 管理的任务。
  • Android 16+ 有“作业配额”限制:如果长时间任务太多,可能会耗尽配额。这时可能需要直接自己启动前台服务,不走 WorkManager。
  • 用户体验要友好:通知里最好带有取消按钮(createCancelPendingIntent()),用户可以中途停止任务。

为什么不实现getForegroundInfo而是调用自定义的 createForegroundInfo ?

getForegroundInfo方法系统在需要让 Worker 马上运行在前台服务时,会主动调用这个方法。典型场景是加急任务。因为加急任务要求立即前台化,否则会抛异常。所以如果你计划让某个 Worker 一开始就必须前台运行,你可以直接重写这个方法,把通知返回给系统。

createForegroundInfo 方法是示例中自己写的一个工具方法,用来封装创建 ForegroundInfo 对象的逻辑。作用只是方便在多个地方调用,不用重复写通知构建逻辑。

  • 上面示例里的上传文件任务不是加急任务,它只是一个长时间运行的普通 Worker。
    所以不需要在 doWork() 开始前就前台化,而是在任务开始执行时调用 setForeground() 就够了。
  • 上传过程中会不断更新进度(开始、50%、80%、完成)。用 createForegroundInfo(progress) 可以在不同阶段传不同的 progress 字符串,动态生成新的通知,然后再调用 setForeground() 更新。
  • 当然也有一些项目在逻辑上将两者结合:在 getForegroundInfo() 提供一个初始通知,然后在任务运行时再调用 setForeground(createForegroundInfo("更新进度...")) 来更新。

4.7. 更新工作

以前如果你要改一个已经在 WorkManager 里排好的任务(WorkRequest),常规做法是:

  1. 先 cancelWorkById()
  2. 再创建一个新的 WorkRequest
  3. 再 enqueue()

4.7.1 尽量避免取消工作

取消工作会出现两个麻烦:

  1. 重复执行:取消后 Worker 内部的计算可能中断,重新来一次成本高。
  2. 丢时间:比如 PeriodicWorkRequest 的时间偏移得重新算。

WorkManager 2.8.0 之后,新增了 updateWork(),可以直接改“已排队”的任务,不需要取消+重建。

什么时候应该“更新”

  1. 小改动:只改约束条件、输入数据等。
  2. 保持 Worker 类型不变:OneTimeWorkRequest 只能改 OneTimeWorkRequest,PeriodicWorkRequest 只能改 PeriodicWorkRequest。
  3. 典型场景:
          · 修改上传任务的充电/Wi-Fi 限制;
          · 调整任务执行条件以配合用户行为;
          · 不想丢失原来任务的状态/进度。

什么时候仍然要“取消+重建”

  1. 你要把 OneTimeWorkRequest 改成 PeriodicWorkRequest(或反过来);
  2. 或者 Worker 的基本性质变了(换成了另一个 Worker 类);

4.7.2 使用 updateWork 更新一次性或定期工作

如需使用更新已加入队列的工作,请按以下步骤操作:

  1. 先拿到旧的 WorkRequest ID
          · 用 getWorkInfosForUniqueWork()、getWorkInfoById() 等;
          · 或自己在最初 enqueue 时保存 WorkRequest.id。
  2. 创建一个新的 WorkRequest,但指定同一个 ID
          · 用 OneTimeWorkRequestBuilder 或 PeriodicWorkRequestBuilder;
          · .setId(旧ID) 来保持与旧任务一致;
          · 改你要改的约束/输入数据。
  3. 调用 workManager.updateWork(newRequest)
          · WorkManager 会用新 WorkRequest 替换掉老的。

示例:

// 第一次的约束条件
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).setRequiresCharging(true).build()
// 第一次构建 WorkRequest
val uploadWorkRequest:OneTimeWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().setConstraints(constraints).setInputData(workDataOf("param" to "第一次参数")).build()
// 第一次入队
WorkManager.getInstance(context).enqueueUniqueWork("UploadLog", ExistingWorkPolicy.REPLACE, uploadWorkRequest)// 1. 获取旧任务的 ID(此处通过唯一工作名来取)
val workInfoList = WorkManager.getInstance(context).getWorkInfosForUniqueWork("UploadLog").get()
val existingId: UUID = workInfoList.firstOrNull()?.id ?: return// 2. 新的约束条件:不再要求充电
val newConstraints = Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).setRequiresCharging(false).build()// 3. 用旧 ID 创建新的 WorkRequest
val newWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().setConstraints(newConstraints).setInputData(workDataOf("param" to "更新后的参数")).setId(existingId).build()// 4. 调用 updateWork 替换旧任务
val updateResult = WorkManager.getInstance(context).updateWork(newWorkRequest)

4.7.3. 更新的版本号

每更新一次 WorkRequest,后,它的 generation(代数)+1。这样你便可以知道当前 WorkRequest 是第几版。

示例:

val workInfo = WorkManager.getInstance(context).getWorkInfoById(existingId).get()
val generation = workInfo?.generation

4.7.4. 使用 UPDATE 更新定期工作

对于周期任务PeriodicWorkRequest 的场景,WorkManager 2.8.0 之后,新增了冲突策略  ExistingPeriodicWorkPolicy.UPDATE 可直接更新 ( OneTimeWorkRequest 没有这个策略,因为一次性任务不会无限运行,不能像周期任务那样“更新”)。

更新行为:

  • 如果存在同名的、尚未完成的任务,则会使用新的配置对其进行更新,并且可保留原来的排队时间。
  • 如果存在同名的、正在运行的任务,这次执行不会被打断,仍然按旧的约束、旧的标签等继续跑完;但下一轮周期执行时会用新的配置。
  • 如果存在同名的、但已取消的任务,旧任务会被删除,再将新的任务入队。
  • 如果不存在同名的任务,新建并入队一条同名任务。

示例:

// 第一次的约束条件
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).setRequiresCharging(true).build()
// 第一次构建周期性 WorkRequest
val uploadLogWorkRequest = PeriodicWorkRequestBuilder<UploadWorker>(24, TimeUnit.HOURS).setConstraints(constraints).addTag("upload").setInputData(workDataOf("param" to "第一次参数")).build()
// 第一次入队
WorkManager.getInstance(context).enqueueUniquePeriodicWork("UploadLog", ExistingPeriodicWorkPolicy.KEEP, uploadLogWorkRequest)// 新的约束条件:不再要求充电
val newConstraints = Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).setRequiresCharging(false).build()
// 新构建周期性 WorkRequest:使用新的约束条件 和 更新 参数
val newUploadLogWorkRequest = PeriodicWorkRequestBuilder<UploadWorker>(24, TimeUnit.HOURS).setConstraints(newConstraints).addTag("upload").setInputData(workDataOf("param" to "更新后的参数")).build()
// 用 UPDATE 更新之前的同名任务
WorkManager.getInstance(context).enqueueUniquePeriodicWork("UploadLog", ExistingPeriodicWorkPolicy.UPDATE, newUploadLogWorkRequest)

4.8. 远程的工作

在默认情况下,WorkManager 的 Worker/CoroutineWorker 都运行在应用的主进程里。
但是有些情况,例如:

  • 任务非常耗时,可能拖慢主进程;
  • 主进程内存紧张,希望隔离;
  • 多进程架构应用需要在不同进程运行任务,

这时你就可以用 RemoteCoroutineWorker。

4.8.1. 继承 RemoteCoroutineWorker

RemoteCoroutineWorker也是 ListenableWorker 的一个实现,它底层封装了跨进程通信。它让你的 Worker 跑在单独的进程里,而不是主进程。其用法和普通 CoroutineWorker 基本一样,只是需要额外配置目标进程。

示例:

class ExampleRemoteCoroutineWorker(context: Context, params: WorkerParameters) : RemoteCoroutineWorker(context, params) {override suspend fun doRemoteWork(): Result {return try {// 模拟耗时任务delay(1000)Result.success()} catch (e: Exception) {Result.failure()}}
}

4.8.2. 让 Worker 跑到指定进程

如何让 Worker 跑到指定进程,关键就是在输入数据(InputData)里,告诉 WorkManager 你要跑在哪个进程。
需要提供两个参数:

  • ARGUMENT_PACKAGE_NAME:包名
  • ARGUMENT_CLASS_NAME:RemoteWorkerService 的类名

示例:

val data = Data.Builder().putString(ARGUMENT_PACKAGE_NAME, context.packageName).putString(ARGUMENT_CLASS_NAME, RemoteWorkerService::class.java.name).build()val exampleRemoteRequest = OneTimeWorkRequest.Builder(ExampleRemoteCoroutineWorker::class.java).setInputData(data).build()WorkManager.getInstance(context).enqueueUniqueWork("ExampleRemote", ExistingWorkPolicy.REPLACE, exampleRemoteRequest)

注意:

RemoteWorkerService 是 androidx.work:work-multiprocess 库中的类,需要另外进行依赖

4.8.3. 声明 RemoteWorkerService

因为任务要跑在独立进程,你必须在 AndroidManifest.xml 里配置服务:

<!-- 绑定到 :worker1 进程 -->
<serviceandroid:name="androidx.work.multiprocess.RemoteWorkerService"android:exported="false"android:process=":worker1" /><!-- 你可以再定义一个新的 RemoteWorkerService 跑在另一个进程 -->
<serviceandroid:name=".RemoteWorkerService2"android:exported="false"android:process=":worker2" />

4.9. 初始化

4.9.1. 默认初始化

WorkManager 的初始化方法是 initialize()。但是在默认情况下,是不需要手动调用的,因为在 WorkManager 库中的 AndroidManifest 中如这样的配置:

<application><providerandroid:name="androidx.startup.InitializationProvider"android:authorities="${applicationId}.androidx-startup"android:exported="false"tools:node="merge" ><meta-dataandroid:name="androidx.work.WorkManagerInitializer"android:value="androidx.startup" /></provider>
……
</application>

androidx.startup.InitializationProvider 是 Android Jetpack架构 App Startup 组件定义的一个用于集中初始化的 ContentProvider,它的 onCreate 方法中会根据配置的 meta-data 内部通过反射的方进行调用其对应类的初始化工作,而此处就是反射到 androidx.work.WorkManagerInitializer 类,该类中的 onCreate 方法中会调用:

WorkManager.initialize(context, new Configuration.Builder().build());

所以这就是大多数项目的情况:直接 WorkManager.getInstance(context) 就能用,不需要 initialize() 的原因。

4.9.2. 自定义初始化

如果你需要自定义配置(如自定义线程池、日志等级等),你需要阻止自动初始化,然后在 Application 里手动调用 WorkManager.initialize()。

步骤1:移除默认初始化程序

WorkManager 通过 ContentProvider 自动初始化。
要关闭它,需要在 AndroidManifest.xml 里用 tools:node="remove" 删除对应节点。

场景 A:你用的是 WorkManager 2.6+(带 androidx.startup)

写法 1:彻底移除

<!-- 移除整个 androidx.startup 初始化 -->
<providerandroid:name="androidx.startup.InitializationProvider"android:authorities="${applicationId}.androidx-startup"tools:node="remove" />

写法 2:只移除 WorkManager 的初始化(保留其他库的 startup)

<providerandroid:name="androidx.startup.InitializationProvider"android:authorities="${applicationId}.androidx-startup"android:exported="false"tools:node="merge"><!-- 只移除 WorkManagerInitializer --><meta-dataandroid:name="androidx.work.WorkManagerInitializer"android:value="androidx.startup"tools:node="remove" />
</provider>

场景 B:你用的是 WorkManager 2.6 之前版本

<providerandroid:name="androidx.work.impl.WorkManagerInitializer"android:authorities="${applicationId}.workmanager-init"tools:node="remove" />

步骤 2:提供自定义 Configuration

方式 1:推荐(自动调用)

让你的 Application 实现 Configuration.Provider:

class MyApplication : Application(), Configuration.Provider {override fun onCreate() {super.onCreate()}override val workManagerConfiguration: Configurationget() = Configuration.Builder().setMinimumLoggingLevel(android.util.Log.INFO)              // 自定义日志级别.setExecutor(Executors.newFixedThreadPool(8))    // 自定义线程池.build()
}
  • 不需要自己调用 WorkManager.initialize()
  • 只要调用 WorkManager.getInstance(context) 就能用
  • 记得先移除默认初始化程序,否则不会生效

方式 2:手动初始化(旧版或特殊场景用)

如果你不想在 Application 里实现接口,可以手动在 Application.onCreate() 中初始化:

class MyApplication : Application() {override fun onCreate() {super.onCreate()val myConfig = Configuration.Builder().setMinimumLoggingLevel(android.util.Log.INFO)              // 自定义日志级别.setExecutor(Executors.newFixedThreadPool(8))    // 自定义线程池.build()// 手动初始化 WorkManagerWorkManager.initialize(this, myConfig)}
}

setMinimumLoggingLevel

设置 WorkManager 的最小日志输出等级。

应用场景:

  • 开发调试,建议 Log.DEBUG(方便排查)
  • 线上生产,建议 Log.INFO 或 Log.WARN(减少日志开销)

setExecutor

指定运行 Worker 的线程池(doWork 方法工作的线程池)。默认情况下 WorkManager 内部有一个后台线程池(大小和设备 CPU 核心数相关),可以换成你自己的线程池,比如固定大小的线程池、限制任务并发等。

应用场景:

  • 你想限制 Worker 并发数(例如只开 2 个线程)
  • 你想和项目里的线程池复用(避免线程池过多)

注意:

如果你定义工作是继承 CoroutineWorker,那么doWork() 是一个“挂起”函数。它不会在 Configuration 中指定的 Executor 中运行,而是默认为 Dispatchers.Default。

setTaskExecutor

指定 WorkManager 内部调度任务时用的线程池。它和 setExecutor 不一样:

setExecutor 是执行真正的 Worker;setTaskExecutor 是执行 WorkManager 内部的调度逻辑(比如约束检查、任务调度)。

应用场景:

  • 一般情况不用改(默认的单线程足够)
  • 如果你的任务量非常大、调度开销大,可以用多线程

5. 总结

WorkManager 是 Google 官方推荐的后台任务调度库,特别适合处理需要 延迟执行 且必须 保证执行 的任务。

  1. 不怕 App 被杀
  2. 不怕手机重启
  3. 不怕低版本兼容
  4. 支持网络、电量等条件限制
  5. 支持链式任务组合、失败重试、状态监听

总之,在各种后台任务场景中,WorkManager 都是一个稳定、安全、灵活的选择。掌握它,将极大提升你对 Android 后台任务的掌控力。

更多详细的 WorkManager介绍,请访问 Android 开发者官网

http://www.dtcms.com/a/427949.html

相关文章:

  • 1、docker入门简介
  • 个人小说网站怎么做娄底企业网站建设制作
  • 三层交换(h3c)
  • 网站有备案 去掉备案网页制作对联
  • 静态网站制作流程怎么查看网站收录
  • 迅为Hi3403V610开发板海思Cortex-A55架构核心板卡
  • 绿建设计院网站软件库网站大全
  • 数学-绝对值(三)
  • ESP32项目(二、笔记本和ESP32点到点通讯)
  • Claude 4.5 Sonnet 全面测评
  • 公司电商网站开发合同电子商务网站建设及推广方案论文
  • 做网站流程内容上海网站运营
  • 2. 守护计划
  • QCustomPlot 核心功能与图表设置(上)——基础样式定制
  • 面经分享--金山软件开发一面
  • java Garbage
  • sward入门到实战(10) - 如何做好文档评审?
  • 网站开发类的合同范本遂宁移动端网站建设
  • 网站备案承诺书怎么写河南 网站建设
  • Anaconda常用操作
  • 政务公开和网站建设自查报告朋友要我帮忙做网站
  • 数据治理4-企业数仓开发标准与规范
  • 深圳网站建设黄浦网络 骗钱服务外包网站
  • 租用微信做拍卖网站律师网站建设建议
  • 后台与网站软文推广渠道主要有
  • 上海网站建设_永灿品牌建站三只松鼠网站推广策略
  • wordpress主题外贸网站专业做网站排名多少钱
  • JoyAgent-JDGenie深度评测:从开箱到实战,多智能体框架的工业级答卷
  • 常州企业自助建站2手房产App网站开发
  • 在线做交互网站番禺免费核酸检测