Впровадження 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). Агресивні кошти економії батареї вбивають фонові процеси. WorkManager використовує JobScheduler, який ці виробники переосмислюють. Єдиний надійний обхідний шлях — setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) для критичних за часом завдань та документування обмежень у README. Гарантування точного часу виконання фонового завдання на цих пристроях неможливо.
Утечка контексту. Worker отримує applicationContext, але розробники іноді захоплюють Activity в лямбдах. Робітники живуть довше за Activities—збої виникають при доступі до знищених контекстів.
Занадто багато даних в 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 тижнів. Вартість розраховується індивідуально.







