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.

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.
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.
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.
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.
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.
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
)
}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.
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().
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()
}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.
Gotowy na rozmowy o Android?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Harmonogramowanie Okresowej Pracy z PeriodicWorkRequest
PeriodicWorkRequest wykonuje się cyklicznie z minimalnym interwałem 15 minut (Android wymusza ten limit).
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
)
}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.
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 -> {}
}
}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.
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 } }
}
}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.
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.
@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())
}
}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?
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.
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.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.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Kluczowe Wnioski dla Produkcyjnego Użycia WorkManager
- Preferuj
CoroutineWorkernadWorkerprzy wywoływaniu funkcji suspend. Unika blokowania wątków i integruje się z istniejącym kodem coroutine. - Używaj
enqueueUniqueWork()aby zapobiec duplikowaniu pracy. WybierzKEEPdla operacji idempotentnych,REPLACEgdy 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
WorkMetricsInfow 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.
Znajdziesz błąd w Android?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 22 sierpnia 2026
Tagi
Udostępnij
Powiązane artykuły

Jetpack Navigation Compose w 2026: Nawigacja Type-Safe i Pytania Rekrutacyjne
Kompleksowy przewodnik po Jetpack Navigation Compose z nawigacją typu type-safe, zaawansowanymi wzorcami i pytaniami rekrutacyjnymi dla programistów Android.

Room Database w Androidzie 2026: Migracje, Relacje i Współbieżność z Coroutines
Kompleksowy przewodnik po Room Database w Androidzie 2026 - od migracji schematu, przez relacje między encjami, po integrację z Kotlin Coroutines i najlepsze praktyki.

Kotlin Flow vs StateFlow vs SharedFlow: pytania rekrutacyjne Android w 2026
Pytania o Kotlin Flow vs StateFlow vs SharedFlow, które rekruterzy Androida zadają w 2026 roku, z jasnymi odpowiedziami, tabelą porównawczą i kodem gotowym do produkcji.