Android WorkManager in 2026: Achtergrondtaken, Constraints en Sollicitatievragen

Complete handleiding voor Android WorkManager 2.11/2.12 met Kotlin-codevoorbeelden, constraints-configuratie, het koppelen van werkverzoeken en veelgestelde technische sollicitatievragen.

Android WorkManager Achtergrondtaken Tutorial 2026

Android WorkManager beheert uitstelbare, gegarandeerde achtergrondtaken die app-herstarts en apparaatopstarts overleven. Als onderdeel van Android Jetpack biedt WorkManager 2.11.2 (stabiel, maart 2026) een uniforme API voor API 23+ die automatisch de beste onderliggende implementatie kiest (JobScheduler, AlarmManager of Firebase JobDispatcher) op basis van het API-niveau van het apparaat.

Wanneer WorkManager gebruiken

WorkManager is geschikt voor taken die gegarandeerde uitvoering vereisen: uploaden van logs, synchroniseren van data, verwerken van afbeeldingen. Voor onmiddellijk werk dat proceseinde niet overleeft, worden Kotlin Coroutines gebruikt.

WorkManager 2.11 opzetten in een Kotlin-project

Voordat een worker wordt geschreven, moet de dependency worden gedeclareerd. WorkManager 2.11+ vereist minSdk 23 en 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")
}

Het work-runtime-ktx-artefact bevat Kotlin-extensies en Coroutine-ondersteuning via CoroutineWorker. Voor basisgebruik is geen extra configuratie nodig: WorkManager initialiseert zichzelf via een ContentProvider.

Een eenvoudige Worker maken met doWork()

Een Worker-klasse overschrijft doWork() en retourneert een Result. Het systeem voert deze methode uit op een achtergrondthread.

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

Drie mogelijke uitkomsten bestaan: Result.success() markeert voltooiing, Result.failure() stopt nieuwe pogingen, en Result.retry() plant opnieuw in volgens het backoff-beleid.

CoroutineWorker gebruiken voor Suspend-functies

Wanneer het werk suspend-functies omvat (Retrofit-aanroepen, Room-queries), elimineert CoroutineWorker geneste callbacks.

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() draait op Dispatchers.Default. Om van dispatcher te wisselen, wordt withContext() binnen de functie gebruikt.

Constraints definiëren voor voorwaardelijke uitvoering

Constraints voorkomen dat werk wordt uitgevoerd totdat aan voorwaarden is voldaan. Dit bespaart batterij en vermijdt mislukte pogingen.

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

Beschikbare constraints omvatten netwerktype (CONNECTED, UNMETERED, METERED, NOT_ROAMING), batterijniveau, oplaadstatus, opslagruimte en apparaat-idle-status. WorkManager 2.10+ accepteert ook een raw NetworkRequest voor fijnmazige netwerkcontrole.

Sollicitatie-inzicht

Een veelgestelde sollicitatievraag betreft wanneer KEEP vs REPLACE te gebruiken in enqueueUniqueWork. KEEP negeert nieuwe verzoeken als werk met dezelfde naam bestaat, REPLACE annuleert bestaand werk en start opnieuw. KEEP wordt gebruikt voor uploads waar duplicaten bandbreedte verspillen, REPLACE voor synchronisaties waar alleen de nieuwste data telt.

Werkverzoeken koppelen met then() en combine()

Complexe workflows vereisen sequentiële en parallelle uitvoering. WorkManager koppelt werkverzoeken met beginWith() en 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()
}

Output van parallelle workers wordt standaard samengevoegd in een ArrayCreatingInputMerger. De volgende worker ontvangt alle key-value-paren, waarbij arrays worden gemaakt voor dubbele sleutels.

Klaar om je Android gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Periodiek werk plannen met PeriodicWorkRequest

PeriodicWorkRequest voert herhaaldelijk uit met een minimuminterval van 15 minuten (Android handhaaft deze limiet).

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

Het flex-venster stelt WorkManager in staat werk te bundelen met andere jobs, wat de batterij-efficiëntie verbetert. Een interval van 6 uur met 30 minuten flex betekent dat uitvoering ergens tussen 5:30 en 6:00 na de vorige run plaatsvindt.

Werkstatus observeren met LiveData en Flow

WorkManager stelt werkstatus beschikbaar via WorkInfo. Observatie gebeurt met LiveData of 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 -> {}
    }
}

Werkstatussen volgen een levenscyclus: ENQUEUED, RUNNING, SUCCEEDED/FAILED/CANCELLED, BLOCKED (wachtend op dependencies).

WorkManager 2.12 Work Metrics API

WorkManager 2.12.0-rc01 (augustus 2026) introduceert WorkMetricsInfo voor het bijhouden van uitvoeringsgeschiedenis.

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

Metrics omvatten uitvoeringsduur, retry-tellingen, stopreden en timestamps. De API maakt het mogelijk intermitterende fouten in productie te debuggen zonder aangepaste logging-infrastructuur.

Metrics-retentie

WorkMetricsInfo-data wordt standaard na 7 dagen verwijderd. Retentie kan worden geconfigureerd met Configuration.Builder().setWorkMetricsRetentionDuration().

Workers testen met WorkManagerTestInitHelper

Unit-tests voor workers vereisen het work-testing-artefact. TestListenableWorkerBuilder maakt workers aan zonder ze in de wachtrij te plaatsen.

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

Voor integratietests die constraints en koppeling verifiëren, wordt TestDriver gebruikt om constraint-vervulling en tijdsverloop te simuleren.

Veelgestelde sollicitatievragen over WorkManager

Technische sollicitatiegesprekken testen vaak het begrip van WorkManager's garanties en trade-offs. Deze vragen verschijnen in Android mid-level en senior interviews.

V: Wat gebeurt er met een WorkRequest als de app wordt afgesloten?

WorkManager slaat werkverzoeken persistent op in een Room-database. Wanneer de app herstart of het systeem constraint-vervulling signaleert, wordt lopend werk hervat. Deze garantie onderscheidt WorkManager van CoroutineScope-werk dat sterft met het proces.

V: Hoe verschilt WorkManager van AlarmManager?

AlarmManager plant exacte tijdalarmen en voert code uit op specifieke kloktijden. WorkManager plant uitstelbaar werk dat wordt uitgevoerd wanneer aan constraints is voldaan, zonder garantie voor exacte timing. AlarmManager wordt gebruikt voor gebruikersgerichte alarmen, WorkManager voor achtergrond-dataverwerking.

V: Kan WorkManager werk onmiddellijk uitvoeren?

Ja, met setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST). Versnelde werkverzoeken gebruiken de foreground service-slot op API 31+ of JobScheduler.setImportantWhileForeground() op oudere APIs. Quota-limieten gelden, dus de OutOfQuotaPolicy definieert fallback-gedrag.

V: Hoe zou men alle lopende uploads annuleren?

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

Tags maken bulk-operaties mogelijk. Alternatief annuleert cancelUniqueWork("name") een specifieke keten.

Systeem-geïnduceerde stops afhandelen met Backoff-beleid

Wanneer het systeem een worker stopt (batterij-optimalisatie, geheugendruk), bepaalt het backoff-beleid de timing voor nieuwe pogingen.

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

Exponentiële backoff verdubbelt de vertraging bij elke retry: 30s, 60s, 120s, tot een maximum van 5 uur. Lineaire backoff voegt de initiële vertraging elke keer toe: 30s, 60s, 90s. De nieuwe setBackoffOnSystemInterruption()-vlag in WorkManager 2.11 past backoff ook toe wanneer het systeem (niet de worker) de stop veroorzaakte.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Belangrijke inzichten voor productie-WorkManager-gebruik

  • CoroutineWorker prefereren boven Worker bij het aanroepen van suspend-functies. Het vermijdt thread-blokkering en integreert met bestaande coroutine-code.
  • enqueueUniqueWork() gebruiken om dubbel werk te voorkomen. KEEP kiezen voor idempotente operaties, REPLACE wanneer alleen het laatste verzoek telt.
  • Betekenisvolle tags op elk verzoek instellen. Tags maken observatie, annulering en debugging over werkketens mogelijk.
  • Workers geïsoleerd testen met TestListenableWorkerBuilder, daarna ketens testen met TestDriver constraint-simulatie.
  • WorkManager 2.12's WorkMetricsInfo-API biedt uitvoeringsgeschiedenis. Deze wordt gebruikt om retry-patronen en stopredenen in productie te debuggen.
  • Constraints besparen batterij. Een worker die faalt door ontbrekend netwerk verspilt CPU-cycli en verbruikt stroom bij retry-pogingen.
  • Het 15-minutenminimum voor periodiek werk wordt afgedwongen door Android, niet WorkManager. Periodieke taken moeten worden ontworpen om dit interval te tolereren.
Dagelijkse challenge

Zie jij de bug in Android?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 22 augustus 2026

Tags

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

Delen

Gerelateerde artikelen