WorkManager 后台任务调度实战:从一次性任务到周期执行
在 Android 应用开发中,经常需要处理一些后台任务,比如数据同步、日志上传、图片压缩等。这些任务可能需要在特定条件下执行,也可能需要定期运行。WorkManager 是 Google 推荐的后台任务解决方案,它能确保任务即使在应用退出或设备重启后依然可靠执行。
为什么选择 WorkManager
在 WorkManager 出现之前,Android 开发者需要根据不同场景选择不同方案:
- 立即执行的短任务:Thread、Executor
- 需要保证执行的任务:JobScheduler(API 21+)、FirebaseJobDispatcher
- 定时任务:AlarmManager
- 长时运行任务:Foreground Service
这些 API 的使用场景和兼容性各不相同,WorkManager 统一了这些需求,提供了一套简洁且向后兼容的 API(最低支持到 API 14)。
WorkManager 的优势
- 保证执行:即使应用退出或设备重启,任务依然会执行
- 约束条件:可设置网络状态、充电状态、存储空间等执行条件
- 链式任务:支持任务串行、并行组合
- 周期任务:支持设置最短间隔的周期执行
- 向后兼容:内部会根据设备 API 级别选择最优实现方式
添加依赖
gradle
dependencies {
def work_version = "2.9.0"
implementation "androidx.work:work-runtime-ktx:$work_version"
}
一次性任务:数据同步示例
定义 Worker
kotlin
class DataSyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
// 从 InputData 获取参数
val userId = inputData.getString("user_id") ?: return Result.failure()
// 执行同步逻辑
val syncResult = syncUserData(userId)
if (syncResult.success) {
// 返回成功,可携带输出数据
val outputData = workDataOf("sync_time" to System.currentTimeMillis())
Result.success(outputData)
} else {
// 返回失败
Result.failure()
}
} catch (e: Exception) {
// 返回重试(WorkManager 会自动重试)
Result.retry()
}
}
private suspend fun syncUserData(userId: String): SyncResult {
// 模拟网络请求
delay(2000)
return SyncResult(success = true)
}
}
data class SyncResult(val success: Boolean)
构建和提交任务
kotlin
// 构建输入数据
val inputData = workDataOf("user_id" to "12345")
// 创建一次性任务请求
val syncRequest = OneTimeWorkRequestBuilder<DataSyncWorker>()
.setInputData(inputData)
.build()
// 提交任务
WorkManager.getInstance(context).enqueue(syncRequest)
观察任务状态
kotlin
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(syncRequest.id)
.observe(lifecycleOwner) { workInfo ->
when (workInfo?.state) {
WorkInfo.State.ENQUEUED -> Log.d("Work", "任务已入队")
WorkInfo.State.RUNNING -> Log.d("Work", "任务执行中")
WorkInfo.State.SUCCEEDED -> {
val syncTime = workInfo.outputData.getLong("sync_time", 0)
Log.d("Work", "任务成功,同步时间: $syncTime")
}
WorkInfo.State.FAILED -> Log.d("Work", "任务失败")
WorkInfo.State.CANCELLED -> Log.d("Work", "任务取消")
else -> {}
}
}
添加约束条件
实际场景中,我们希望任务在特定条件下执行,比如只在 WiFi 网络下上传日志:
kotlin
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // WiFi 或无流量消耗网络
.setRequiresBatteryNotLow(true) // 电量不低
.setRequiresStorageNotLow(true) // 存储空间充足
.setRequiresCharging(false) // 不要求充电
.build()
val uploadRequest = OneTimeWorkRequestBuilder<LogUploadWorker>()
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueue(uploadRequest)
常见约束类型:
- NetworkType :
NOT_REQUIRED、CONNECTED、UNMETERED、NOT_ROAMING、METERED - 电量 :
setRequiresBatteryNotLow(true) - 充电 :
setRequiresCharging(true) - 存储 :
setRequiresStorageNotLow(true) - 设备空闲 (API 23+):
setRequiresDeviceIdle(true)
周期任务:定时数据清理
周期任务的最短间隔是 15 分钟,这是系统为了省电做的限制:
kotlin
class DataCleanupWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
cleanupOldData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
private suspend fun cleanupOldData() {
val database = AppDatabase.getInstance(applicationContext)
val cutoffTime = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L // 7天前
database.logDao().deleteOlderThan(cutoffTime)
}
}
创建周期任务:
kotlin
val cleanupRequest = PeriodicWorkRequestBuilder<DataCleanupWorker>(
repeatInterval = 1, // 间隔
repeatIntervalTimeUnit = TimeUnit.DAYS // 单位
).build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"data_cleanup", // 唯一名称
ExistingPeriodicWorkPolicy.KEEP, // 如果已存在则保持
cleanupRequest
)
ExistingPeriodicWorkPolicy 选项:
- KEEP:保持已存在的任务,忽略新请求
- REPLACE:取消并删除已存在的任务,用新任务替换
- UPDATE:保持已存在任务的状态和进度,只更新配置
链式任务:图片处理流水线
假设需要:下载图片 → 压缩 → 添加水印 → 上传
kotlin
// 1. 下载图片
val downloadRequest = OneTimeWorkRequestBuilder<DownloadImageWorker>()
.setInputData(workDataOf("image_url" to "https://example.com/photo.jpg"))
.build()
// 2. 压缩图片
val compressRequest = OneTimeWorkRequestBuilder<CompressImageWorker>().build()
// 3. 添加水印
val watermarkRequest = OneTimeWorkRequestBuilder<WatermarkImageWorker>().build()
// 4. 上传图片
val uploadRequest = OneTimeWorkRequestBuilder<UploadImageWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
// 构建链式任务
WorkManager.getInstance(context)
.beginWith(downloadRequest) // 第一步
.then(compressRequest) // 第二步
.then(watermarkRequest) // 第三步
.then(uploadRequest) // 第四步
.enqueue()
并行任务
如果有多个独立任务可以并行执行:
kotlin
val taskA = OneTimeWorkRequestBuilder<WorkerA>().build()
val taskB = OneTimeWorkRequestBuilder<WorkerB>().build()
val taskC = OneTimeWorkRequestBuilder<WorkerC>().build()
val finalTask = OneTimeWorkRequestBuilder<FinalWorker>().build()
// taskA 和 taskB 并行执行,都完成后再执行 finalTask
WorkManager.getInstance(context)
.beginWith(listOf(taskA, taskB))
.then(finalTask)
.enqueue()
任务重试与退避策略
当任务返回 Result.retry() 时,WorkManager 会自动重试,默认使用指数退避策略:
kotlin
val retryRequest = OneTimeWorkRequestBuilder<RetryableWorker>()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL, // 或 LINEAR
30, // 初始延迟
TimeUnit.SECONDS
)
.build()
- EXPONENTIAL:指数退避,重试间隔为 30s、60s、120s...
- LINEAR:线性退避,每次增加固定时长
唯一任务:避免重复
使用 enqueueUniqueWork 可以确保同一名称的任务不会重复执行:
kotlin
val syncRequest = OneTimeWorkRequestBuilder<DataSyncWorker>().build()
WorkManager.getInstance(context).enqueueUniqueWork(
"user_data_sync", // 唯一名称
ExistingWorkPolicy.KEEP, // 策略
syncRequest
)
ExistingWorkPolicy 选项:
- REPLACE:取消已存在的任务,用新任务替换
- KEEP:保持已存在的任务,忽略新请求
- APPEND:将新任务追加到已存在任务链的末尾
- APPEND_OR_REPLACE:如果已存在任务链失败或取消,则替换;否则追加
取消任务
kotlin
// 根据 ID 取消
WorkManager.getInstance(context).cancelWorkById(workRequest.id)
// 根据唯一名称取消
WorkManager.getInstance(context).cancelUniqueWork("user_data_sync")
// 根据 Tag 取消
WorkManager.getInstance(context).cancelAllWorkByTag("sync")
// 取消所有任务
WorkManager.getInstance(context).cancelAllWork()
实战案例:离线数据上报
场景:App 需要在网络可用时上报用户行为日志,要求:
- 只在 WiFi 下上传
- 每小时尝试一次
- 任务失败后自动重试
- 避免重复上传
实现
kotlin
class LogUploadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val database = AppDatabase.getInstance(applicationContext)
val pendingLogs = database.logDao().getPendingLogs()
if (pendingLogs.isEmpty()) {
return Result.success()
}
return try {
val api = RetrofitClient.create<LogApiService>()
val response = api.uploadLogs(pendingLogs)
if (response.isSuccessful) {
database.logDao().markAsUploaded(pendingLogs.map { it.id })
Result.success()
} else {
Result.retry()
}
} catch (e: Exception) {
Result.retry()
}
}
}
// 提交周期任务
fun scheduleLogUpload(context: Context) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.build()
val uploadRequest = PeriodicWorkRequestBuilder<LogUploadWorker>(
1, TimeUnit.HOURS
)
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
15, TimeUnit.MINUTES
)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"log_upload",
ExistingPeriodicWorkPolicy.KEEP,
uploadRequest
)
}
调试技巧
查看任务状态
kotlin
WorkManager.getInstance(context)
.getWorkInfosByTagLiveData("sync")
.observe(lifecycleOwner) { workInfoList ->
workInfoList.forEach { workInfo ->
Log.d("Work", "ID: ${workInfo.id}, State: ${workInfo.state}")
}
}
启用日志
在 Application 类中配置:
kotlin
class MyApp : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.build()
}
记得在 AndroidManifest.xml 中禁用默认初始化:
xml
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
常见问题
任务为什么没有立即执行?
WorkManager 不是实时任务调度器,系统会根据约束条件和设备状态决定执行时机。如果需要立即执行任务,使用 setExpedited() 标记为加急任务(API 23+)。
周期任务的实际间隔为什么大于设置值?
系统为了省电会批量处理任务,实际执行时间可能晚于预期。如果需要精确定时,考虑使用 AlarmManager + BroadcastReceiver。
如何在 Worker 中访问 ViewModel 或 Repository?
通过依赖注入(Hilt)或者自定义 WorkerFactory:
kotlin
class DaggerWorkerFactory @Inject constructor(
private val repository: UserRepository
) : WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
return when (workerClassName) {
DataSyncWorker::class.java.name ->
DataSyncWorker(appContext, workerParameters, repository)
else -> null
}
}
}
小结
WorkManager 是 Android 后台任务的推荐方案,适用于需要保证执行的场景。关键点:
- 一次性任务用
OneTimeWorkRequest,周期任务用PeriodicWorkRequest - 通过
Constraints设置执行条件 - 链式任务可以串行或并行组合
- 唯一任务避免重复执行
- 根据业务需求选择重试策略和退避算法
对于需要立即执行、精确定时或需要前台服务的场景,WorkManager 可能不是最佳选择,需要结合 Coroutines、AlarmManager 或 Foreground Service 等方案。