WorkManager 是 Jetpack 中用于处理可延迟但必须执行 的后台任务的调度库。它能保证任务在所有场景下被执行——即使应用退出、设备重启。WorkManager 内部根据 API 级别智能选择底层调度器。
一、为什么需要 WorkManager 1.1 Android 后台任务调度演进史 在 WorkManager 诞生之前,Android 开发者面对后台任务调度时需要根据不同的 API 级别选择不同的 API:
API 级别
可用调度 API
局限性
所有
Thread / Handler / AsyncTask(已废弃)
进程被杀后任务丢失
所有
Service + startForeground()
前台 Service 需要常驻通知;后台 Service (API 26+ 受限)
所有
AlarmManager
精确闹钟受 Doze 模式限制;API 19+ 非精确闹钟
API 9+
BroadcastReceiver + AlarmManager
需要在 Manifest 注册,管理复杂
API 21+
JobScheduler
API 21+ 才可用;API < 21 需要兼容方案
API 14+(Firebase)
FirebaseJobDispatcher
依赖 Google Play Services,国内设备不可用
这造成了严重的碎片化 —— 开发者需要为同一功能编写多套实现:
fun scheduleSync (context: Context ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val jobInfo = JobInfo.Builder(1 , ComponentName(context, SyncJobService::class .java)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPeriodic(TimeUnit.HOURS.toMillis(6 )) .build() jobScheduler.schedule(jobInfo) } else { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(context, SyncReceiver::class .java) val pendingIntent = PendingIntent.getBroadcast(context, 0 , intent, 0 ) alarmManager.setInexactRepeating( AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + TimeUnit.HOURS.toMillis(6 ), TimeUnit.HOURS.toMillis(6 ), pendingIntent ) } }
WorkManager 的出现统一了这一切:开发者只需用一套 API 定义任务,WorkManager 内部根据设备 API 级别和 Google Play Services 可用性自动选择最优调度器。
1.2 WorkManager 的设计哲学 WorkManager 解决的核心问题是 Android 上可延迟但必须执行 的工作(deferrable guaranteed work):
可延迟 :工作不需要立即执行,可以在满足约束条件后在后台运行。不是 UI 相关的即时操作。
必须执行 :即使应用退出、设备重启,任务也必须在某个时刻完成。JobScheduler 在 API 21-22 的设备上会遇到任务丢失问题,WorkManager 内部有持久化机制保证不丢失。
约束驱动 :任务只在满足条件时才执行(有网络、电量充足、设备空闲等),避免在不合适的时间点消耗资源。
WorkManager 不适合的场景:
需要精确时间触发的任务 → 使用 AlarmManager(如闹钟应用)
需要立即执行且不需要持久化的任务 → 使用 CoroutineScope + Dispatchers
需要前台执行的长时任务 → 使用前台 Service 或 WorkManager 的 setForeground()(ExpeditedWork)
二、核心组件详解 2.1 Worker —— 任务的执行单元 Worker 是实际执行任务的类。通常使用 CoroutineWorker(基于 Kotlin 协程):
class UploadLogWorker ( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { override suspend fun doWork () : Result { val logPath = inputData.getString("log_path" ) ?: return Result.failure( workDataOf("error" to "log_path is missing" ) ) val maxRetries = inputData.getInt("max_retries" , 3 ) return try { val uploadedSize = uploadLogFile(logPath) Result.success( workDataOf( "uploaded_size" to uploadedSize, "uploaded_at" to System.currentTimeMillis() ) ) } catch (e: IOException) { if (runAttemptCount < maxRetries) { Result.retry() } else { Result.failure( workDataOf( "error" to e.message ?: "Unknown IO error" , "attempt_count" to runAttemptCount ) ) } } catch (e: Exception) { Result.failure( workDataOf("error" to (e.message ?: "Unknown error" )) ) } } private suspend fun uploadLogFile (path: String ) : Long { val file = File(path) if (!file.exists()) { throw IOException("Log file not found: $path " ) } return file.length() } }
CoroutineWorker 的线程模型:
doWork() 默认运行在 Dispatchers.Default 上(CPU 密集型线程池)
如需切换到 Dispatchers.IO(网络/文件操作),使用 withContext(Dispatchers.IO) { ... }
切勿在 doWork() 中直接操作 UI 组件 —— WorkManager 的运行环境可能没有 UI 线程
Worker 的两种实现方式:
基类
线程
协程支持
推荐度
Worker
后台线程(Executor 线程池)
不支持(需手动用 runBlocking 或 ListenableFuture)
仅 Java 项目使用
CoroutineWorker
Dispatchers.Default
原生 suspend 支持
推荐 (Kotlin 项目首选)
ListenableWorker
自定义
自定义
需要与 RxJava/ListenableFuture 集成时使用
2.2 WorkRequest —— 任务描述 WorkRequest 描述了”执行什么 Worker、何时执行、有哪些约束”。
OneTimeWorkRequest —— 一次性任务:
val uploadWork = OneTimeWorkRequestBuilder<UploadLogWorker>() .build() val uploadWorkFull = OneTimeWorkRequestBuilder<UploadLogWorker>() .setInputData( workDataOf( "log_path" to "/data/data/com.example/files/logs/app.log" , "max_retries" to 5 , "compress_first" to true ) ) .setConstraints(constraints) .setInitialDelay(10 , TimeUnit.SECONDS) .setBackoffCriteria( BackoffPolicy.EXPONENTIAL, WorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS ) .addTag("upload_log" ) .addTag("periodic_sync" ) .build() WorkManager.getInstance(context).enqueue(uploadWorkFull)
PeriodicWorkRequest —— 周期任务:
val periodicSyncWork = PeriodicWorkRequestBuilder<SyncWorker>( 6 , TimeUnit.HOURS, 15 , TimeUnit.MINUTES ) .setConstraints( Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresCharging(true ) .build() ) .addTag("periodic_sync" ) .build() WorkManager.getInstance(context) .enqueueUniquePeriodicWork( "daily_sync" , ExistingPeriodicWorkPolicy.UPDATE, periodicSyncWork )
flexInterval 解释:
PeriodicWork: repeatInterval = 6 hours, flexInterval = 15 minutes 时间轴: [ 0s ............................ 5h45m ][ 15m ][ 6h00m ................] ↑________↑ flex window(弹性窗口) 任务在这 15 分钟内执行均满足要求
设置 flexInterval 允许 WorkManager 在满足约束条件时弹性安排任务执行时间,而非严格按固定时间触发。这有助于系统进行批量调度(batch),节省电量。
2.3 WorkRequest 的三种唯一性策略 当多次提交同名任务时,通过 ExistingWorkPolicy 控制行为:
WorkManager.getInstance(context) .enqueueUniqueWork( "upload_photos" , ExistingWorkPolicy.REPLACE, uploadWork ) WorkManager.getInstance(context) .enqueueUniqueWork( "upload_photos" , ExistingWorkPolicy.KEEP, uploadWork ) WorkManager.getInstance(context) .enqueueUniqueWork( "upload_photos" , ExistingWorkPolicy.APPEND, uploadWork ) WorkManager.getInstance(context) .enqueueUniquePeriodicWork( "daily_sync" , ExistingPeriodicWorkPolicy.UPDATE, periodicWork )
三、约束条件(Constraints) 3.1 完整约束类型 val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true ) .setRequiresCharging(true ) .setRequiresDeviceIdle(true ) .setRequiresStorageNotLow(true ) .build()
约束组合策略:
val syncConstraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresBatteryNotLow(true ) .build() val logConstraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresDeviceIdle(true ) .build() val prefetchConstraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresCharging(true ) .build()
3.2 约束满足与调度时序 WorkManager 不会轮询检查约束是否满足。约束满足检测依赖于系统的广播事件:
网络状态变化 → ConnectivityManager 回调 → WorkManager 检查 pending 任务 充电状态变化 → BatteryManager 广播 → WorkManager 检查 pending 任务 设备空闲 → DeviceIdleMode 回调 → WorkManager 检查 pending 任务
这意味着:如果一个任务因为”需要 WiFi 网络”而被阻塞,当用户连接到 WiFi 时,WorkManager 会在收到系统回调后立即(少数秒内)执行该任务,而不是等到下一个周期才检查。
四、任务链式调度 4.1 顺序链 val downloadWork = OneTimeWorkRequestBuilder<DownloadWorker>().build()val verifyWork = OneTimeWorkRequestBuilder<VerifyWorker>().build()val decryptWork = OneTimeWorkRequestBuilder<DecryptWorker>().build()val processWork = OneTimeWorkRequestBuilder<ProcessWorker>().build()WorkManager.getInstance(context) .beginWith(downloadWork) .then(verifyWork) .then(decryptWork) .then(processWork) .enqueue()
4.2 复杂链(并联 + 串联) val downloadA = OneTimeWorkRequestBuilder<DownloadWorkerA>().build()val downloadB = OneTimeWorkRequestBuilder<DownloadWorkerB>().build()val downloadC = OneTimeWorkRequestBuilder<DownloadWorkerC>().build()val mergeWork = OneTimeWorkRequestBuilder<MergeWorker>().build()val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>().build()WorkManager.getInstance(context) .beginWith(listOf(downloadA, downloadB, downloadC)) .then(mergeWork) .then(uploadWork) .enqueue()
4.3 Worker 间数据传递 WorkManager 通过 Data 对象传递数据:
class GenerateReportWorker ( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { override suspend fun doWork () : Result { val reportPath = generateReport() return Result.success( workDataOf( "report_path" to reportPath, "report_size" to File(reportPath).length(), "generated_at" to System.currentTimeMillis() ) ) } } class UploadReportWorker ( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { override suspend fun doWork () : Result { val reportPath = inputData.getString("report_path" ) ?: return Result.failure() val reportSize = inputData.getLong("report_size" , 0L ) uploadFile(reportPath) return Result.success( workDataOf("uploaded_at" to System.currentTimeMillis()) ) } } WorkManager.getInstance(context) .beginWith(reportWork) .then(uploadWork) .enqueue()
Data 的限制:
最大 10KB(内部使用 Bundle 序列化,受 Binder 事务限制)
支持的类型:Int、Long、Float、Double、Boolean、String、StringArray
大数据应在 Worker 内部通过文件路径或数据库传递,而非直接放入 Data
五、任务状态机与观察 5.1 WorkManager 的状态模型 每个 WorkRequest 都有一个生命周期,经历以下状态流转:
┌──────────┐ │ BLOCKED │ ← 有前置任务未完成,或约束条件未满足 └────┬─────┘ │ 前置任务完成 + 约束满足 ▼ ┌───────────┐ │ ENQUEUED │ ← 任务已入队,等待执行 └─────┬─────┘ │ 轮到该任务执行 ▼ ┌───────────┐ │ RUNNING │ ← 正在执行 doWork() └─┬─────┬───┘ │ │ ┌────────┘ └────────┐ ▼ ▼ ┌──────────┐ ┌──────────┐ │ SUCCEEDED │ │ FAILED │ ← doWork() 返回 Result.failure() └──────────┘ └──────────┘ ↑ ┌──────┴──────┐ │ RETRY? │ ← doWork() 返回 Result.retry() └──────┬──────┘ │ yes(未超过重试次数) └────→ ENQUEUED
5.2 观察任务状态 WorkManager.getInstance(context) .getWorkInfoByIdLiveData(uploadWork.id) .observe(lifecycleOwner) { workInfo -> when (workInfo?.state) { WorkInfo.State.ENQUEUED -> Log.d(TAG, "任务已入队" ) WorkInfo.State.RUNNING -> { val progress = workInfo.progress.getInt("percent" , 0 ) updateProgressBar(progress) } WorkInfo.State.SUCCEEDED -> { val size = workInfo.outputData.getLong("uploaded_size" , 0 ) showUploadSuccess(size) } WorkInfo.State.FAILED -> { val error = workInfo.outputData.getString("error" ) ?: "未知错误" showUploadFailed(error) } WorkInfo.State.BLOCKED -> Log.d(TAG, "任务被阻塞" ) WorkInfo.State.CANCELLED -> Log.d(TAG, "任务已取消" ) } } WorkManager.getInstance(context) .getWorkInfosByTagLiveData("sync_group" ) .observe(lifecycleOwner) { workInfoList -> val allFinished = workInfoList.all { it.state.isFinished } val anyFailed = workInfoList.any { it.state == WorkInfo.State.FAILED } } WorkManager.getInstance(context) .getWorkInfosForUniqueWorkLiveData("upload_photos" ) .observe(lifecycleOwner) { workInfoList -> workInfoList.forEach { workInfo -> Log.d(TAG, "工作 ${workInfo.id} : ${workInfo.state} " ) } } viewModelScope.launch { WorkManager.getInstance(context) .getWorkInfoByIdFlow(uploadWork.id) .collect { workInfo -> _uiState.value = when (workInfo?.state) { WorkInfo.State.RUNNING -> UiState.Uploading WorkInfo.State.SUCCEEDED -> UiState.Success WorkInfo.State.FAILED -> UiState.Error else -> UiState.Idle } } }
5.3 进度更新 class ProgressWorker ( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { override suspend fun doWork () : Result { val totalImages = 100 for (i in 1. .totalImages) { processImage(i) val progress = workDataOf( "percent" to (i * 100 / totalImages), "current" to i, "total" to totalImages ) setProgress(progress) if (isStopped) { return Result.failure() } } return Result.success() } }
六、ExpeditedWork 与前台执行 6.1 为什么需要 ExpeditedWork 默认情况下,后台任务可能被系统延迟执行(Doze 模式、App Standby Bucket 等)。但有些任务需要尽快完成:
用户主动触发的”立即同步”
应用关闭前的状态保存
关键的日志上报(崩溃恢复场景)
ExpeditedWork 在 Android 12+(API 31+)上使用系统级的加急作业(expedited job),在旧设备上使用前台 Service 作为回退方案。
val expeditedWork = OneTimeWorkRequestBuilder<SyncWorker>() .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .build() WorkManager.getInstance(context).enqueue(expeditedWork)
OutOfQuotaPolicy 选项:
RUN_AS_NON_EXPEDITED_WORK_REQUEST:当应用超出加急配额时,降级为普通任务
DROP_WORK_REQUEST:当超出配额时直接丢弃任务
6.2 前台 Service 关联 对于需要长时间运行且用户需要感知的任务(如文件下载),可以使用 setForeground():
class DownloadWorker ( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { override suspend fun doWork () : Result { setForeground( ForegroundInfo( notificationId = 1001 , createDownloadNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC ) ) return try { downloadLargeFile { progress -> setForeground( ForegroundInfo( 1001 , createDownloadNotification(progress) ) ) } Result.success() } catch (e: Exception) { Result.failure() } } private fun createDownloadNotification (progress: Int = 0 ) : Notification { return NotificationCompat.Builder(applicationContext, CHANNEL_ID) .setSmallIcon(R.drawable.ic_download) .setContentTitle("正在下载" ) .setContentText("已下载 $progress %" ) .setProgress(100 , progress, false ) .setOngoing(true ) .build() } }
七、底层调度机制深入 7.1 调度器选择逻辑 WorkManager 内部调度器的选择优先级如下:
1. 如果有 Google Play Services → 使用 GcmNetworkManager (但实际上从 WorkManager 2.3+ 开始不再使用 GCM) 2. API 23+ → 使用 JobScheduler 3. API 14-22 → 使用 AlarmManager + BroadcastReceiver 内部实现代码位于 androidx.work.impl.background.systemjob.SystemJobScheduler 和 androidx.work.impl.background.systemalarm.SystemAlarmScheduler
7.2 任务持久化机制 WorkManager 将任务信息持久化到内部 Room 数据库中(默认数据库名 workmanager.db):
CREATE TABLE WorkSpec ( id TEXT PRIMARY KEY , state INTEGER NOT NULL , worker_class_name TEXT NOT NULL , input_data BLOB , output_data BLOB , initial_delay INTEGER NOT NULL , interval_duration INTEGER NOT NULL , flex_duration INTEGER NOT NULL , run_attempt_count INTEGER NOT NULL , backoff_policy INTEGER NOT NULL , backoff_delay_duration INTEGER , period_start_time INTEGER , schedule_requested_at INTEGER , run_duration_nanos INTEGER , constraints BLOB );
当应用进程被杀或设备重启后,WorkManager 读取 WorkSpec 表,恢复所有未完成的任务。
7.3 退避策略(Backoff Policy) 当 Worker 返回 Result.retry() 时,WorkManager 按退避策略延迟重试:
setBackoffCriteria( BackoffPolicy.LINEAR, WorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS ) setBackoffCriteria( BackoffPolicy.EXPONENTIAL, WorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS )
八、测试 8.1 使用 TestListenableWorkerBuilder 测试 Worker @RunWith(AndroidJUnit4::class) class UploadLogWorkerTest { private lateinit var context: Context @Before fun setup () { context = ApplicationProvider.getApplicationContext() } @Test fun testUploadSuccess () = runTest { val worker = TestListenableWorkerBuilder<UploadLogWorker>(context) .setInputData( workDataOf( "log_path" to "/test/path/log.txt" , "max_retries" to 3 ) ) .build() val result = worker.doWork() assertThat(result, `is `(ListenableWorker.Result.success())) } @Test fun testUploadFailure_exhaustedRetries () = runTest { val worker = TestListenableWorkerBuilder<UploadLogWorker>(context) .setInputData( workDataOf("log_path" to "/nonexistent/file.log" ) ) .setRunAttemptCount(5 ) .build() val result = worker.doWork() assertThat(result, instanceOf(ListenableWorker.Result.Failure::class .java)) } }
8.2 使用 TestDriver 控制延迟 @RunWith(AndroidJUnit4::class) class WorkManagerTest { private lateinit var workManager: WorkManager private lateinit var testDriver: TestDriver @Before fun setup () { val context = ApplicationProvider.getApplicationContext<Context>() val config = Configuration.Builder() .setMinimumLoggingLevel(Log.DEBUG) .setExecutor(SynchronousExecutor()) .build() WorkManagerTestInitHelper.initializeTestWorkManager(context, config) workManager = WorkManager.getInstance(context) testDriver = WorkManagerTestInitHelper.getTestDriver(context)!! } @Test fun testDelayedWork () = runTest { val work = OneTimeWorkRequestBuilder<TestWorker>() .setInitialDelay(10 , TimeUnit.MINUTES) .build() workManager.enqueue(work).result.get () testDriver.setInitialDelayMet(work.id) testDriver.setAllConstraintsMet(work.id) val workInfo = workManager.getWorkInfoById(work.id).get () assertThat(workInfo.state, `is `(WorkInfo.State.SUCCEEDED)) } }
九、WorkManager vs 其他调度方案
方案
精确时间
保证执行
系统约束
持久化
适用场景
WorkManager
否
是
支持
是
大部分后台任务
JobScheduler
否
有限保证
支持
有限
API 21+,WorkManager 底层
AlarmManager
是(受限)
否
否
否
闹钟/提醒/精确定时
DownloadManager
否
是
网络
是
大文件下载
Foreground Service
立即
是
需通知
否
用户可感知的长时任务
CoroutineWorker
立即
否
否
否
应用内即时异步任务
面试常考问题 Q1:WorkManager 与 Service、JobScheduler、AlarmManager 的区别?
WorkManager 是最上层抽象封装,内部根据 API 级别自动路由:
API 23+ → JobScheduler
API 14-22 → AlarmManager + BroadcastReceiver
同时提供约束系统、链式调度、状态观察、任务持久化等高级功能。Service 适合前台任务,AlarmManager 适合精确定时(如闹钟),JobScheduler 适合条件触发的后台任务但仅限 API 21+ 且无持久化保证。
Q2:doWork() 在哪个线程执行?
Worker 的 doWork() 默认在 WorkManager 的内部 Executor 线程池上执行(后台线程)。CoroutineWorker 的 doWork() 在 Dispatchers.Default 上运行。切勿在 doWork() 中直接操作 UI 组件。如需在主线程更新 UI,应用 LiveData/Observer 观察任务状态。
Q3:PeriodicWorkRequest 最小间隔为何是 15 分钟?
为了减少后台唤醒对电池的消耗,Android 强制 PeriodicWorkRequest 的最小间隔为 15 分钟(MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L)。这是 Android 系统级的限制,WorkManager 内部在 WorkSpec 中定义了该常量。如需更短间隔的后台任务,应使用带超时的前台 Service,或考虑 Handler.postDelayed() + AlarmManager(注意 Doze 限制)。
Q4:WorkManager 如何保证任务在设备重启后仍被执行?
WorkManager 将所有任务信息(WorkSpec)持久化到 Room 数据库中。设备启动后,WorkManager 在初始化时读取数据库中的待执行任务,重新注册到底层调度器(JobScheduler 或 AlarmManager)。这也是为什么 WorkManager 需要 android.permission.RECEIVE_BOOT_COMPLETED 权限 —— 如果使用了 AlarmManager 调度器(API < 23)。
Q5:如何取消多个任务?
通过 id 取消:WorkManager.getInstance(context).cancelWorkById(workRequest.id)
通过 tag 取消:WorkManager.getInstance(context).cancelAllWorkByTag("sync_group")
通过唯一名称取消:WorkManager.getInstance(context).cancelUniqueWork("upload_photos")
取消所有任务:WorkManager.getInstance(context).cancelAllWork()
取消是异步的 —— cancelWorkById() 返回的 ListenableFuture 完成后,任务状态才变为 CANCELLED。注意:已经在 RUNNING 状态的任务不会被强制中断,Worker 应定期检查 isStopped 来优雅退出。
Q6:WorkManager 如何处理 Doze 模式?
在 Doze 模式下,系统会延迟所有网络访问和 CPU 密集操作。WorkManager 通过以下策略处理 Doze:
将任务委托给 JobScheduler(API 23+),后者原生理解 Doze 模式
在 Doze 的维护窗口(Maintenance Window)内集中执行积压任务
使用 WorkManager.periodicWorkRequest 时,任务可能在维护窗口内执行,而非严格按时触发
ExpeditedWork 可以在 Doze 模式下获得优先执行权(有配额限制)