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 diagram showing background task scheduling with constraints and work chains

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.

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")
}

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.

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()
        }
    }
}

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.

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() 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.

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
        )
}

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().

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()
}

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).

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
        )
}

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.

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 -> {}
    }
}

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.

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 } }
    }
}

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.

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())
    }
}

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.

BackoffConfig.ktkotlin
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 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.
Wyzwanie dnia

Znajdziesz błąd w Android?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 22 sierpnia 2026

Tagi

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

Udostępnij

Powiązane artykuły