Реализация WorkManager для фоновых задач в Android-приложении
WorkManager — стандартный API для фоновых задач, которые должны выполняться гарантированно: загрузка файлов, синхронизация данных, отправка аналитики. Это не замена Coroutines для разовых операций — WorkManager нужен там, где задача должна завершиться даже если приложение убито или устройство перезагружено.
Основные концепции
Worker / CoroutineWorker — единица работы. WorkRequest — задача с настройками. WorkManager — планировщик, реализованный поверх JobScheduler (API 23+), AlarmManager (старые версии) и Firebase JobDispatcher (если доступен). Выбор между ними — автоматический.
CoroutineWorker — предпочтительный вариант для Kotlin:
class SyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
val userId = inputData.getString(KEY_USER_ID) ?: return Result.failure()
syncRepository.syncUser(userId)
Result.success()
} catch (e: IOException) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
companion object {
const val KEY_USER_ID = "user_id"
}
}
runAttemptCount — счётчик попыток. Result.retry() вместе с BackoffPolicy определяет, через сколько времени WorkManager повторит задачу.
Настройка задачи
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(workDataOf(SyncWorker.KEY_USER_ID to userId))
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.MINUTES)
.addTag("sync_task")
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
"user_sync_$userId",
ExistingWorkPolicy.KEEP,
syncRequest
)
enqueueUniqueWork с ExistingWorkPolicy.KEEP — не добавляет дублирующую задачу, если уже есть активная с тем же именем. Без этого пользователь, тапнувший кнопку «Синхронизировать» дважды, запускает два параллельных воркера.
Периодические задачи
val periodicRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.build()
)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"hourly_sync",
ExistingPeriodicWorkPolicy.UPDATE,
periodicRequest
)
Минимальный интервал для PeriodicWorkRequest — 15 минут. Короче — система игнорирует и запускает по своему расписанию. ExistingPeriodicWorkPolicy.UPDATE (появился в WorkManager 2.8.0) обновляет настройки существующей периодической задачи, не отменяя её.
Цепочки задач
WorkManager.getInstance(context)
.beginUniqueWork("upload_chain", ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<CompressWorker>().build()
)
.then(OneTimeWorkRequestBuilder<UploadWorker>().build())
.then(OneTimeWorkRequestBuilder<NotifyWorker>().build())
.enqueue()
Если CompressWorker возвращает Result.failure() — цепочка останавливается, UploadWorker не запускается. Result.success(outputData) передаёт данные в следующий воркер через inputMerger.
Наблюдение за статусом
WorkManager.getInstance(context)
.getWorkInfosByTagLiveData("sync_task")
.observe(viewLifecycleOwner) { workInfos ->
workInfos?.forEach { info ->
when (info.state) {
WorkInfo.State.RUNNING -> showProgress()
WorkInfo.State.SUCCEEDED -> showSuccess()
WorkInfo.State.FAILED -> showError()
else -> Unit
}
}
}
Проблемы, с которыми сталкиваются в проектах
Задача не запускается на китайских устройствах (Xiaomi, Huawei). Агрессивные battery saver убивают фоновые процессы. WorkManager использует JobScheduler, который эти производители переопределяют. Единственный надёжный обходной путь — setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) для задач, критичных к задержке, и документирование ограничений в README. Обещать пользователю точное время выполнения фоновой задачи на этих устройствах — невозможно.
Утечка контекста. Worker получает applicationContext, но разработчики иногда захватывают Activity через лямбды. Воркер живёт дольше Activity — крэш при обращении к уничтоженному контексту.
Слишком большой объём данных в inputData. Лимит — 10 КБ. Передавать ID, а не сериализованные объекты. Данные читать из Room или SharedPreferences внутри воркера.
Интеграция с Hilt
@HiltWorker
class SyncWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val syncRepository: SyncRepository
) : CoroutineWorker(context, params) { ... }
// В Application
@HiltAndroidApp
class App : Application(), Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder().setWorkerFactory(workerFactory).build()
}
Без кастомной Configuration.Provider Hilt не может инжектировать зависимости в Worker — WorkManager создаёт воркеры через свою фабрику по умолчанию.
Реализация WorkManager с базовым набором задач — 2-4 дня. Сложные цепочки с обработкой ошибок, синхронизацией и тестами — до 2 недель. Стоимость рассчитывается индивидуально.







