# Android WorkManager w 2026: Zadania w Tle, Ograniczenia i Pytania Rekrutacyjne > Kompleksowy przewodnik po WorkManager 2.11 w Kotlinie - od podstawowej konfiguracji przez ograniczenia i łańcuchy zadań po testowanie i pytania na rozmowy kwalifikacyjne. - Published: 2026-08-22 - Updated: 2026-08-22 - Author: Anthony Fillion-Maillet - Tags: android, workmanager, kotlin, background-tasks, jetpack - Reading time: 12 min --- Android WorkManager obsługuje odroczone, gwarantowane zadania w tle, które przetrwają restarty aplikacji i ponowne uruchomienia urządzenia. Wydany jako część Android Jetpack, WorkManager 2.11.2 (stabilna wersja, marzec 2026) dostarcza zunifikowane API działające na API 23+, automatycznie wybierając najlepszą implementację (JobScheduler, AlarmManager lub Firebase JobDispatcher) w zależności od poziomu API urządzenia. > **Kiedy używać WorkManager** > > WorkManager należy stosować do zadań wymagających gwarantowanego wykonania: wysyłanie logów, synchronizacja danych, przetwarzanie obrazów. Dla natychmiastowej pracy, która nie musi przetrwać śmierci procesu, lepszym wyborem są Kotlin Coroutines. ## Konfiguracja WorkManager 2.11 w Projekcie Kotlin Przed napisaniem jakiegokolwiek workera należy zadeklarować zależność. WorkManager 2.11+ wymaga minSdk 23 i compileSdk 33. ```gradle // build.gradle.kts (app module) dependencies { val workVersion = "2.11.2" implementation("androidx.work:work-runtime-ktx:$workVersion") // Optional: for testing androidTestImplementation("androidx.work:work-testing:$workVersion") } ``` Artefakt `work-runtime-ktx` zawiera rozszerzenia Kotlin i wsparcie dla Coroutines poprzez `CoroutineWorker`. Dodatkowa konfiguracja nie jest wymagana dla podstawowego użycia: WorkManager inicjalizuje się automatycznie przez ContentProvider. ## Tworzenie Prostego Workera z doWork() Klasa `Worker` nadpisuje metodę `doWork()` i zwraca `Result`. System uruchamia tę metodę w wątku w tle. ```kotlin // SyncWorker.kt 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() } } } ``` Istnieją trzy możliwe rezultaty: `Result.success()` oznacza zakończenie, `Result.failure()` zatrzymuje ponowne próby, a `Result.retry()` harmonogramuje ponowne wykonanie zgodnie z polityką backoff. ## Używanie CoroutineWorker dla Funkcji Suspend Gdy praca obejmuje funkcje suspend (wywołania Retrofit, zapytania Room), `CoroutineWorker` eliminuje zagnieżdżanie callbacków. ```kotlin // UploadWorker.kt 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()` działa na `Dispatchers.Default`. Aby przełączyć dispatcher, należy użyć `withContext()` wewnątrz funkcji. ## Definiowanie Ograniczeń dla Warunkowego Wykonania Ograniczenia zapobiegają uruchomieniu pracy do momentu spełnienia warunków. Oszczędza to baterię i unika nieudanych prób. ```kotlin // ScheduleUpload.kt fun scheduleUpload(context: Context, imageUri: String) { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) // WiFi only .setRequiresBatteryNotLow(true) .setRequiresStorageNotLow(true) .build() val uploadRequest = OneTimeWorkRequestBuilder() .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 ) } ``` Dostępne ograniczenia obejmują typ sieci (CONNECTED, UNMETERED, METERED, NOT_ROAMING), poziom baterii, stan ładowania, dostępne miejsce na dysku i stan bezczynności urządzenia. WorkManager 2.10+ akceptuje również surowy `NetworkRequest` dla precyzyjnej kontroli sieci. > **Wskazówka rekrutacyjna** > > Częste pytanie rekrutacyjne dotyczy różnicy między `KEEP` a `REPLACE` w `enqueueUniqueWork`. `KEEP` ignoruje nowe żądania, jeśli praca o tej samej nazwie istnieje, `REPLACE` anuluje istniejącą pracę i rozpoczyna od nowa. `KEEP` stosuje się dla uploadów, gdzie duplikaty marnują przepustowość, `REPLACE` dla synchronizacji, gdzie liczy się tylko najnowsze dane. ## Łączenie Żądań Pracy z then() i combine() Złożone przepływy pracy wymagają sekwencyjnego i równoległego wykonania. WorkManager łączy żądania pracy używając `beginWith()` i `then()`. ```kotlin // WorkChain.kt fun processAndUploadImages(context: Context, imageUris: List) { val workManager = WorkManager.getInstance(context) // Parallel compression workers val compressRequests = imageUris.map { uri -> OneTimeWorkRequestBuilder() .setInputData(workDataOf("uri" to uri)) .build() } // Single upload worker runs after all compressions complete val uploadRequest = OneTimeWorkRequestBuilder() .setConstraints( Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() ) .build() // Cleanup runs after upload, regardless of success val cleanupRequest = OneTimeWorkRequestBuilder() .build() workManager .beginWith(compressRequests) // Parallel .then(uploadRequest) // Sequential .then(cleanupRequest) // Sequential .enqueue() } ``` Wyjście z równoległych workerów jest domyślnie scalane przez `ArrayCreatingInputMerger`. Następny worker otrzymuje wszystkie pary klucz-wartość, z tablicami tworzonymi dla duplikatów kluczy. ## Harmonogramowanie Okresowej Pracy z PeriodicWorkRequest PeriodicWorkRequest wykonuje się cyklicznie z minimalnym interwałem 15 minut (Android wymusza ten limit). ```kotlin // PeriodicSync.kt fun scheduleDailySync(context: Context) { val syncRequest = PeriodicWorkRequestBuilder( 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 ) } ``` Okno elastyczności pozwala WorkManager grupować pracę z innymi zadaniami, poprawiając wydajność baterii. Interwał 6-godzinny z 30-minutowym oknem elastyczności oznacza, że wykonanie nastąpi między 5:30 a 6:00 po poprzednim uruchomieniu. ## Obserwowanie Stanu Pracy z LiveData i Flow WorkManager udostępnia stan pracy poprzez `WorkInfo`. Można go obserwować używając LiveData lub Kotlin Flow. ```kotlin // WorkObserver.kt class UploadViewModel(application: Application) : AndroidViewModel(application) { private val workManager = WorkManager.getInstance(application) // Flow-based observation fun observeUpload(workId: UUID): Flow { return workManager.getWorkInfoByIdFlow(workId) } // Check if any upload is running val activeUploads: Flow> = 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 -> {} } } ``` Stany pracy podążają za cyklem życia: ENQUEUED, RUNNING, SUCCEEDED/FAILED/CANCELLED, BLOCKED (oczekiwanie na zależności). ## WorkManager 2.12 Work Metrics API WorkManager 2.12.0-rc01 (sierpień 2026) wprowadza `WorkMetricsInfo` do śledzenia historii wykonania. ```kotlin // MetricsExample.kt class MetricsRepository(private val context: Context) { private val workManager = WorkManager.getInstance(context) suspend fun getWorkerMetrics(workerName: String): List { val query = WorkMetricsQuery.Builder() .setWorkerClassName(workerName) .setLimit(100) .build() return workManager.getWorkMetrics(query) } fun analyzeFailures(metrics: List): Map { // Count stop reasons return metrics .flatMap { it.stopReasonCounts.entries } .groupBy { it.key } .mapValues { entry -> entry.value.sumOf { it.value } } } } ``` Metryki obejmują czas wykonania, liczniki ponownych prób, powody zatrzymania i znaczniki czasu. API umożliwia debugowanie sporadycznych awarii w produkcji bez własnej infrastruktury logowania. > **Retencja metryk** > > Dane WorkMetricsInfo są domyślnie usuwane po 7 dniach. Retencję można skonfigurować poprzez `Configuration.Builder().setWorkMetricsRetentionDuration()`. ## Testowanie Workerów z WorkManagerTestInitHelper Testowanie jednostkowe workerów wymaga artefaktu `work-testing`. `TestListenableWorkerBuilder` tworzy workery bez ich kolejkowania. ```kotlin // SyncWorkerTest.kt @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(context) .setInputData(inputData) .build() val result = worker.doWork() assertThat(result).isEqualTo(ListenableWorker.Result.success()) } @Test fun syncWorker_withoutUserId_fails() = runTest { val worker = TestListenableWorkerBuilder(context) .build() val result = worker.doWork() assertThat(result).isEqualTo(ListenableWorker.Result.failure()) } } ``` Dla testów integracyjnych weryfikujących ograniczenia i łańcuchy, `TestDriver` służy do symulacji spełnienia ograniczeń i upływu czasu. ## Popularne Pytania Rekrutacyjne o WorkManager Rozmowy techniczne często testują zrozumienie gwarancji i kompromisów WorkManager. Te pytania pojawiają się na rozmowach dla programistów Android na poziomie mid i senior. **P: Co się dzieje z WorkRequest, gdy aplikacja zostanie zamknięta?** WorkManager zapisuje żądania pracy w bazie danych Room. Gdy aplikacja zostanie ponownie uruchomiona lub system zasygnalizuje spełnienie ograniczeń, oczekująca praca zostaje wznowiona. Ta gwarancja odróżnia WorkManager od pracy w `CoroutineScope`, która umiera wraz z procesem. **P: Czym WorkManager różni się od AlarmManager?** AlarmManager planuje alarmy o dokładnym czasie i uruchamia kod o określonych porach zegara. WorkManager planuje odroczoną pracę, która uruchamia się po spełnieniu ograniczeń, bez gwarancji dokładnego czasu. AlarmManager stosuje się dla alarmów widocznych dla użytkownika, WorkManager dla przetwarzania danych w tle. **P: Czy WorkManager może uruchomić pracę natychmiast?** Tak, z `setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)`. Przyspieszone żądania pracy używają slotu usługi pierwszoplanowej na API 31+ lub `JobScheduler.setImportantWhileForeground()` na starszych API. Obowiązują limity kwot, więc `OutOfQuotaPolicy` definiuje zachowanie awaryjne. **P: Jak anulować wszystkie oczekujące uploady?** ```kotlin WorkManager.getInstance(context).cancelAllWorkByTag("upload") ``` Tagi umożliwiają operacje zbiorcze. Alternatywnie, `cancelUniqueWork("name")` anuluje konkretny łańcuch. ## Obsługa Zatrzymań Systemowych z Politykami Backoff Gdy system zatrzymuje workera (optymalizacja baterii, presja pamięci), polityka backoff określa czas ponownej próby. ```kotlin // BackoffConfig.kt val request = OneTimeWorkRequestBuilder() .setBackoffCriteria( BackoffPolicy.EXPONENTIAL, Duration.ofSeconds(30) // Initial delay, minimum 10 seconds ) .setBackoffOnSystemInterruption(true) // New in 2.11 .build() ``` Wykładniczy backoff podwaja opóźnienie przy każdej ponownej próbie: 30s, 60s, 120s, do maksymalnie 5 godzin. Liniowy backoff dodaje początkowe opóźnienie za każdym razem: 30s, 60s, 90s. Nowa flaga `setBackoffOnSystemInterruption()` w WorkManager 2.11 stosuje backoff nawet gdy to system (nie worker) spowodował zatrzymanie. ## Kluczowe Wnioski dla Produkcyjnego Użycia WorkManager - Preferuj `CoroutineWorker` nad `Worker` przy wywoływaniu funkcji suspend. Unika blokowania wątków i integruje się z istniejącym kodem coroutine. - Używaj `enqueueUniqueWork()` aby zapobiec duplikowaniu pracy. Wybierz `KEEP` dla operacji idempotentnych, `REPLACE` gdy liczy się tylko ostatnie żądanie. - Ustawiaj znaczące tagi na każdym żądaniu. Tagi umożliwiają obserwację, anulowanie i debugowanie w łańcuchach pracy. - Testuj workery w izolacji z `TestListenableWorkerBuilder`, następnie testuj łańcuchy z symulacją ograniczeń `TestDriver`. - API `WorkMetricsInfo` w WorkManager 2.12 dostarcza historię wykonania. Używaj go do debugowania wzorców ponownych prób i powodów zatrzymań w produkcji. - Ograniczenia oszczędzają baterię. Worker, który zawodzi z powodu braku sieci, marnuje cykle CPU i wyczerpuje energię przy ponownych próbach. - Minimum 15 minut dla okresowej pracy jest wymuszane przez Android, nie WorkManager. Okresowe zadania należy projektować z tolerancją dla tego interwału. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/android/android-workmanager-background-tasks-2026