Android WorkManager у 2026: Фонові Завдання, Обмеження та Питання на Співбесіду

Повний посібник з WorkManager 2.11 на Kotlin - від базового налаштування через обмеження та ланцюги завдань до тестування та питань на технічних співбесідах.

Android WorkManager diagram showing background task scheduling with constraints and work chains

Android WorkManager обробляє відкладені, гарантовані фонові завдання, які зберігаються після перезапуску додатка та перезавантаження пристрою. Випущений як частина Android Jetpack, WorkManager 2.11.2 (стабільна версія, березень 2026) надає уніфікований API, що працює на API 23+, автоматично обираючи найкращу реалізацію (JobScheduler, AlarmManager або Firebase JobDispatcher) залежно від рівня API пристрою.

Коли використовувати WorkManager

WorkManager слід використовувати для завдань, що вимагають гарантованого виконання: надсилання логів, синхронізація даних, обробка зображень. Для негайної роботи, яка не повинна переживати смерть процесу, краще підходять Kotlin Coroutines.

Налаштування WorkManager 2.11 у Kotlin Проекті

Перед написанням будь-якого worker потрібно оголосити залежність. WorkManager 2.11+ вимагає minSdk 23 та compileSdk 33.

build.gradle.kts (app module)gradle
dependencies {
    val workVersion = "2.11.2"
    implementation("androidx.work:work-runtime-ktx:$workVersion")
    // Optional: for testing
    androidTestImplementation("androidx.work:work-testing:$workVersion")
}

Артефакт work-runtime-ktx містить розширення Kotlin та підтримку Coroutines через CoroutineWorker. Додаткова конфігурація для базового використання не потрібна: WorkManager ініціалізується автоматично через ContentProvider.

Створення Простого Worker з doWork()

Клас Worker перевизначає метод doWork() і повертає Result. Система виконує цей метод у фоновому потоці.

SyncWorker.ktkotlin
class SyncWorker(
    context: Context,
    params: WorkerParameters
) : Worker(context, params) {

    override fun doWork(): Result {
        // Read input data
        val userId = inputData.getString("user_id") ?: return Result.failure()
        
        return try {
            // Perform sync operation
            val syncService = SyncService.getInstance(applicationContext)
            syncService.syncUserData(userId)
            Result.success()
        } catch (e: IOException) {
            // Retry on network errors
            Result.retry()
        } catch (e: Exception) {
            // Permanent failure
            Result.failure()
        }
    }
}

Існує три можливі результати: Result.success() позначає завершення, Result.failure() зупиняє повторні спроби, а Result.retry() планує повторне виконання відповідно до політики backoff.

Використання CoroutineWorker для Suspend Функцій

Коли робота включає suspend функції (виклики Retrofit, запити Room), CoroutineWorker усуває вкладення callback.

UploadWorker.ktkotlin
class UploadWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val imageUri = inputData.getString("image_uri")
            ?: return Result.failure()
        
        // Progress reporting (visible in WorkInfo observers)
        setProgress(workDataOf("status" to "compressing"))
        
        val compressed = ImageCompressor.compress(imageUri)
        
        setProgress(workDataOf("status" to "uploading"))
        
        return try {
            val response = ApiClient.imageService.upload(compressed)
            val outputData = workDataOf("url" to response.imageUrl)
            Result.success(outputData)
        } catch (e: HttpException) {
            if (e.code() in 500..599) Result.retry()
            else Result.failure()
        }
    }
}

CoroutineWorker.doWork() виконується на Dispatchers.Default. Для зміни dispatcher використовується withContext() всередині функції.

Визначення Обмежень для Умовного Виконання

Обмеження запобігають виконанню роботи до виконання умов. Це економить батарею та уникає невдалих спроб.

ScheduleUpload.ktkotlin
fun scheduleUpload(context: Context, imageUri: String) {
    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.UNMETERED) // WiFi only
        .setRequiresBatteryNotLow(true)
        .setRequiresStorageNotLow(true)
        .build()

    val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
        .setConstraints(constraints)
        .setInputData(workDataOf("image_uri" to imageUri))
        .setBackoffCriteria(
            BackoffPolicy.EXPONENTIAL,
            Duration.ofMinutes(1)
        )
        .addTag("upload")
        .build()

    WorkManager.getInstance(context)
        .enqueueUniqueWork(
            "upload_$imageUri",
            ExistingWorkPolicy.KEEP,
            uploadRequest
        )
}

Доступні обмеження включають тип мережі (CONNECTED, UNMETERED, METERED, NOT_ROAMING), рівень батареї, стан зарядки, доступний простір на диску та стан простою пристрою. WorkManager 2.10+ також приймає сирий NetworkRequest для точного контролю мережі.

Порада для співбесіди

Поширене питання на співбесіді стосується різниці між KEEP та REPLACE у enqueueUniqueWork. KEEP ігнорує нові запити, якщо робота з такою назвою вже існує, REPLACE скасовує існуючу роботу і починає заново. KEEP використовується для завантажень, де дублікати марнують пропускну здатність, REPLACE для синхронізації, де важливі лише останні дані.

Ланцюгування Запитів Роботи з then() та combine()

Складні робочі процеси вимагають послідовного та паралельного виконання. WorkManager ланцюгує запити роботи за допомогою beginWith() та then().

WorkChain.ktkotlin
fun processAndUploadImages(context: Context, imageUris: List<String>) {
    val workManager = WorkManager.getInstance(context)

    // Parallel compression workers
    val compressRequests = imageUris.map { uri ->
        OneTimeWorkRequestBuilder<CompressWorker>()
            .setInputData(workDataOf("uri" to uri))
            .build()
    }

    // Single upload worker runs after all compressions complete
    val uploadRequest = OneTimeWorkRequestBuilder<BatchUploadWorker>()
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        )
        .build()

    // Cleanup runs after upload, regardless of success
    val cleanupRequest = OneTimeWorkRequestBuilder<CleanupWorker>()
        .build()

    workManager
        .beginWith(compressRequests) // Parallel
        .then(uploadRequest)          // Sequential
        .then(cleanupRequest)          // Sequential
        .enqueue()
}

Вивід з паралельних worker об'єднується за замовчуванням через ArrayCreatingInputMerger. Наступний worker отримує всі пари ключ-значення, при цьому для дублікатів ключів створюються масиви.

Готовий до співбесід з Android?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Планування Періодичної Роботи з PeriodicWorkRequest

PeriodicWorkRequest виконується повторно з мінімальним інтервалом 15 хвилин (Android примусово застосовує цей ліміт).

PeriodicSync.ktkotlin
fun scheduleDailySync(context: Context) {
    val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
        repeatInterval = 6, 
        repeatIntervalTimeUnit = TimeUnit.HOURS,
        flexTimeWindow = 30,
        flexTimeUnit = TimeUnit.MINUTES
    )
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        )
        .addTag("periodic_sync")
        .build()

    WorkManager.getInstance(context)
        .enqueueUniquePeriodicWork(
            "daily_sync",
            ExistingPeriodicWorkPolicy.UPDATE,
            syncRequest
        )
}

Вікно гнучкості дозволяє WorkManager групувати роботу з іншими завданнями, покращуючи ефективність батареї. 6-годинний інтервал з 30-хвилинним вікном гнучкості означає, що виконання відбудеться між 5:30 та 6:00 після попереднього запуску.

Спостереження за Станом Роботи з LiveData та Flow

WorkManager надає стан роботи через WorkInfo. Його можна спостерігати за допомогою LiveData або Kotlin Flow.

WorkObserver.ktkotlin
class UploadViewModel(application: Application) : AndroidViewModel(application) {

    private val workManager = WorkManager.getInstance(application)

    // Flow-based observation
    fun observeUpload(workId: UUID): Flow<WorkInfo?> {
        return workManager.getWorkInfoByIdFlow(workId)
    }

    // Check if any upload is running
    val activeUploads: Flow<List<WorkInfo>> = 
        workManager.getWorkInfosByTagFlow("upload")
            .map { workInfos ->
                workInfos.filter { it.state == WorkInfo.State.RUNNING }
            }
}

// In Compose UI
@Composable
fun UploadProgress(workId: UUID, viewModel: UploadViewModel) {
    val workInfo by viewModel.observeUpload(workId)
        .collectAsState(initial = null)

    when (workInfo?.state) {
        WorkInfo.State.RUNNING -> {
            val status = workInfo?.progress?.getString("status") ?: "working"
            CircularProgressIndicator()
            Text(status)
        }
        WorkInfo.State.SUCCEEDED -> {
            val url = workInfo?.outputData?.getString("url")
            Text("Uploaded: $url")
        }
        WorkInfo.State.FAILED -> Text("Upload failed")
        else -> {}
    }
}

Стани роботи слідують життєвому циклу: ENQUEUED, RUNNING, SUCCEEDED/FAILED/CANCELLED, BLOCKED (очікування залежностей).

WorkManager 2.12 Work Metrics API

WorkManager 2.12.0-rc01 (серпень 2026) вводить WorkMetricsInfo для відстеження історії виконання.

MetricsExample.ktkotlin
class MetricsRepository(private val context: Context) {
    
    private val workManager = WorkManager.getInstance(context)

    suspend fun getWorkerMetrics(workerName: String): List<WorkMetricsInfo> {
        val query = WorkMetricsQuery.Builder()
            .setWorkerClassName(workerName)
            .setLimit(100)
            .build()
        
        return workManager.getWorkMetrics(query)
    }

    fun analyzeFailures(metrics: List<WorkMetricsInfo>): Map<Int, Int> {
        // Count stop reasons
        return metrics
            .flatMap { it.stopReasonCounts.entries }
            .groupBy { it.key }
            .mapValues { entry -> entry.value.sumOf { it.value } }
    }
}

Метрики включають тривалість виконання, лічильники повторних спроб, причини зупинки та мітки часу. API дозволяє відлагоджувати періодичні збої у продакшені без власної інфраструктури логування.

Зберігання метрик

Дані WorkMetricsInfo за замовчуванням видаляються через 7 днів. Налаштуйте зберігання через Configuration.Builder().setWorkMetricsRetentionDuration().

Тестування Worker з WorkManagerTestInitHelper

Юніт-тестування worker вимагає артефакту work-testing. TestListenableWorkerBuilder створює worker без їх додавання в чергу.

SyncWorkerTest.ktkotlin
@RunWith(AndroidJUnit4::class)
class SyncWorkerTest {

    private lateinit var context: Context

    @Before
    fun setup() {
        context = ApplicationProvider.getApplicationContext()
        val config = Configuration.Builder()
            .setMinimumLoggingLevel(Log.DEBUG)
            .setExecutor(SynchronousExecutor())
            .build()
        WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
    }

    @Test
    fun syncWorker_withValidUserId_succeeds() = runTest {
        val inputData = workDataOf("user_id" to "123")
        
        val worker = TestListenableWorkerBuilder<SyncWorker>(context)
            .setInputData(inputData)
            .build()

        val result = worker.doWork()
        
        assertThat(result).isEqualTo(ListenableWorker.Result.success())
    }

    @Test
    fun syncWorker_withoutUserId_fails() = runTest {
        val worker = TestListenableWorkerBuilder<SyncWorker>(context)
            .build()

        val result = worker.doWork()
        
        assertThat(result).isEqualTo(ListenableWorker.Result.failure())
    }
}

Для інтеграційних тестів, що перевіряють обмеження та ланцюгування, TestDriver використовується для симуляції виконання обмежень та плину часу.

Поширені Питання про WorkManager на Співбесіді

Технічні співбесіди часто тестують розуміння гарантій та компромісів WorkManager. Ці питання з'являються на співбесідах для Android розробників рівня middle та senior.

П: Що відбувається з WorkRequest, коли додаток закривається?

WorkManager зберігає запити роботи в базі даних Room. Коли додаток перезапускається або система сигналізує про виконання обмежень, очікувана робота відновлюється. Ця гарантія відрізняє WorkManager від роботи в CoroutineScope, яка помирає разом з процесом.

П: Чим WorkManager відрізняється від AlarmManager?

AlarmManager планує точні за часом будильники та виконує код у визначені моменти часу. WorkManager планує відкладену роботу, яка виконується при виконанні обмежень, без гарантії точного часу. AlarmManager використовується для будильників, видимих користувачеві, WorkManager для фонової обробки даних.

П: Чи може WorkManager виконати роботу негайно?

Так, з setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST). Прискорені запити роботи використовують слот foreground service на API 31+ або JobScheduler.setImportantWhileForeground() на старіших API. Застосовуються обмеження квоти, тому OutOfQuotaPolicy визначає резервну поведінку.

П: Як скасувати всі очікувані завантаження?

kotlin
WorkManager.getInstance(context).cancelAllWorkByTag("upload")

Теги дозволяють масові операції. Альтернативно, cancelUniqueWork("name") скасовує конкретний ланцюг.

Обробка Системних Зупинок з Політиками Backoff

Коли система зупиняє worker (оптимізація батареї, навантаження пам'яті), політика backoff визначає час повторної спроби.

BackoffConfig.ktkotlin
val request = OneTimeWorkRequestBuilder<UploadWorker>()
    .setBackoffCriteria(
        BackoffPolicy.EXPONENTIAL,
        Duration.ofSeconds(30) // Initial delay, minimum 10 seconds
    )
    .setBackoffOnSystemInterruption(true) // New in 2.11
    .build()

Експоненційний backoff подвоює затримку при кожній повторній спробі: 30с, 60с, 120с, до максимуму 5 годин. Лінійний backoff додає початкову затримку кожного разу: 30с, 60с, 90с. Новий прапорець setBackoffOnSystemInterruption() у WorkManager 2.11 застосовує backoff навіть коли зупинку спричинила система (не worker).

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Ключові Висновки для Використання WorkManager у Продакшені

  • Віддавайте перевагу CoroutineWorker над Worker при виклику suspend функцій. Це уникає блокування потоків та інтегрується з існуючим кодом coroutine.
  • Використовуйте enqueueUniqueWork() для запобігання дублювання роботи. Обирайте KEEP для ідемпотентних операцій, REPLACE коли важливий лише останній запит.
  • Встановлюйте значущі теги на кожному запиті. Теги дозволяють спостереження, скасування та відлагодження в ланцюгах роботи.
  • Тестуйте worker ізольовано з TestListenableWorkerBuilder, потім тестуйте ланцюги з симуляцією обмежень TestDriver.
  • API WorkMetricsInfo у WorkManager 2.12 надає історію виконання. Використовуйте його для відлагодження патернів повторних спроб та причин зупинки у продакшені.
  • Обмеження економлять батарею. Worker, який зазнає невдачі через відсутність мережі, витрачає цикли CPU та споживає енергію при повторних спробах.
  • 15-хвилинний мінімум для періодичної роботи встановлюється Android, а не WorkManager. Періодичні завдання слід проектувати з урахуванням цього інтервалу.
Щоденний виклик

Чи знайдеш ти помилку в Android?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 22 серпня 2026 р.

Теги

#android
#workmanager
#kotlin
#background-tasks
#jetpack

Поділитися

Пов'язані статті