Android WorkManager nel 2026: Task in Background, Constraints e Domande da Colloquio
Guida completa ad Android WorkManager 2.11/2.12 con esempi di codice Kotlin, configurazione dei constraints, concatenazione di richieste di lavoro e domande frequenti nei colloqui tecnici.

Android WorkManager gestisce lavori in background differibili e garantiti che persistono attraverso riavvii dell'app e del dispositivo. Rilasciato come parte di Android Jetpack, WorkManager 2.11.2 (stabile, marzo 2026) fornisce un'API unificata che funziona su API 23+ scegliendo automaticamente la migliore implementazione sottostante (JobScheduler, AlarmManager o Firebase JobDispatcher) in base al livello API del dispositivo.
WorkManager è indicato per task che necessitano di esecuzione garantita: caricamento di log, sincronizzazione dati, elaborazione immagini. Per lavori immediati che non sopravvivono alla morte del processo, si utilizzano invece le Kotlin Coroutines.
Configurare WorkManager 2.11 in un progetto Kotlin
Prima di scrivere qualsiasi worker, è necessario dichiarare la dipendenza. WorkManager 2.11+ richiede minSdk 23 e compileSdk 33.
dependencies {
val workVersion = "2.11.2"
implementation("androidx.work:work-runtime-ktx:$workVersion")
// Optional: for testing
androidTestImplementation("androidx.work:work-testing:$workVersion")
}L'artefatto work-runtime-ktx include estensioni Kotlin e supporto per Coroutine attraverso CoroutineWorker. Non è necessaria alcuna configurazione aggiuntiva per l'utilizzo base: WorkManager si inizializza automaticamente tramite un ContentProvider.
Creare un Worker semplice con doWork()
Una classe Worker sovrascrive doWork() e restituisce un Result. Il sistema esegue questo metodo su un thread in background.
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()
}
}
}Esistono tre possibili risultati: Result.success() indica il completamento, Result.failure() interrompe i tentativi, e Result.retry() ripianifica secondo la policy di backoff.
Usare CoroutineWorker per funzioni Suspend
Quando il lavoro coinvolge funzioni suspend (chiamate Retrofit, query Room), CoroutineWorker elimina l'annidamento dei callback.
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() viene eseguito su Dispatchers.Default. Per cambiare dispatcher, si utilizza withContext() all'interno della funzione.
Definire Constraints per l'esecuzione condizionale
I constraints impediscono l'esecuzione del lavoro finché le condizioni non sono soddisfatte. Questo risparmia batteria ed evita tentativi falliti.
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
)
}I constraints disponibili includono tipo di rete (CONNECTED, UNMETERED, METERED, NOT_ROAMING), livello batteria, stato di ricarica, spazio di archiviazione e stato idle del dispositivo. WorkManager 2.10+ accetta anche un NetworkRequest raw per un controllo di rete più granulare.
Una domanda frequente nei colloqui riguarda quando usare KEEP vs REPLACE in enqueueUniqueWork. KEEP ignora nuove richieste se esiste già lavoro con lo stesso nome, REPLACE cancella il lavoro esistente e ricomincia. Si usa KEEP per upload dove i duplicati sprecano banda, REPLACE per sincronizzazioni dove contano solo i dati più recenti.
Concatenare richieste di lavoro con then() e combine()
Workflow complessi richiedono esecuzione sequenziale e parallela. WorkManager concatena le richieste di lavoro usando beginWith() e 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()
}L'output dei worker paralleli viene unito in un ArrayCreatingInputMerger di default. Il worker successivo riceve tutte le coppie chiave-valore, con array creati per chiavi duplicate.
Pronto a superare i tuoi colloqui su Android?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Pianificare lavoro periodico con PeriodicWorkRequest
PeriodicWorkRequest esegue ripetutamente con un intervallo minimo di 15 minuti (Android impone questo limite).
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
)
}La finestra flex consente a WorkManager di raggruppare il lavoro con altri job, migliorando l'efficienza della batteria. Un intervallo di 6 ore con flex di 30 minuti significa che l'esecuzione avviene tra le 5:30 e le 6:00 dopo l'esecuzione precedente.
Osservare lo stato del lavoro con LiveData e Flow
WorkManager espone lo stato del lavoro attraverso WorkInfo. L'osservazione avviene usando LiveData o 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 -> {}
}
}Gli stati del lavoro seguono un ciclo di vita: ENQUEUED, RUNNING, SUCCEEDED/FAILED/CANCELLED, BLOCKED (in attesa di dipendenze).
WorkManager 2.12 Work Metrics API
WorkManager 2.12.0-rc01 (agosto 2026) introduce WorkMetricsInfo per tracciare lo storico delle esecuzioni.
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 } }
}
}Le metriche includono durata dell'esecuzione, conteggio dei retry, motivi di stop e timestamp. L'API permette di debuggare errori intermittenti in produzione senza infrastruttura di logging personalizzata.
I dati di WorkMetricsInfo vengono eliminati dopo 7 giorni di default. La retention può essere configurata con Configuration.Builder().setWorkMetricsRetentionDuration().
Testare i Worker con WorkManagerTestInitHelper
I test unitari dei worker richiedono l'artefatto work-testing. TestListenableWorkerBuilder crea worker senza accodarli.
@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())
}
}Per i test di integrazione che verificano constraints e concatenazione, si usa TestDriver per simulare il soddisfacimento dei constraints e il passaggio del tempo.
Domande frequenti nei colloqui su WorkManager
I colloqui tecnici testano frequentemente la comprensione delle garanzie e dei trade-off di WorkManager. Queste domande appaiono nei colloqui Android di livello mid e senior.
D: Cosa succede a un WorkRequest quando l'app viene terminata?
WorkManager persiste le richieste di lavoro in un database Room. Quando l'app si riavvia o il sistema segnala il soddisfacimento dei constraints, il lavoro in sospeso riprende. Questa garanzia distingue WorkManager dal lavoro in CoroutineScope che muore con il processo.
D: Come si differenzia WorkManager da AlarmManager?
AlarmManager pianifica allarmi a tempo esatto ed esegue codice a orari specifici. WorkManager pianifica lavoro differibile che viene eseguito quando i constraints sono soddisfatti, senza garanzia di timing esatto. Si usa AlarmManager per allarmi rivolti all'utente, WorkManager per l'elaborazione dati in background.
D: WorkManager può eseguire lavoro immediatamente?
Sì, con setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST). Le richieste di lavoro expedited usano lo slot del foreground service su API 31+ o JobScheduler.setImportantWhileForeground() su API precedenti. Si applicano limiti di quota, quindi OutOfQuotaPolicy definisce il comportamento di fallback.
D: Come si cancellerebbero tutti gli upload in sospeso?
WorkManager.getInstance(context).cancelAllWorkByTag("upload")I tag abilitano operazioni massive. In alternativa, cancelUniqueWork("name") cancella una catena specifica.
Gestire gli stop indotti dal sistema con policy di Backoff
Quando il sistema ferma un worker (ottimizzazione batteria, pressione di memoria), la policy di backoff determina il timing dei retry.
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
Duration.ofSeconds(30) // Initial delay, minimum 10 seconds
)
.setBackoffOnSystemInterruption(true) // New in 2.11
.build()Il backoff esponenziale raddoppia il ritardo ad ogni retry: 30s, 60s, 120s, fino a un massimo di 5 ore. Il backoff lineare aggiunge il ritardo iniziale ogni volta: 30s, 60s, 90s. Il nuovo flag setBackoffOnSystemInterruption() in WorkManager 2.11 applica il backoff anche quando il sistema (non il worker) ha causato lo stop.
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Punti chiave per l'uso di WorkManager in produzione
- Preferire
CoroutineWorkeraWorkerquando si chiamano funzioni suspend. Evita il blocco dei thread e si integra con il codice coroutine esistente. - Usare
enqueueUniqueWork()per prevenire lavoro duplicato. ScegliereKEEPper operazioni idempotenti,REPLACEquando conta solo l'ultima richiesta. - Impostare tag significativi su ogni richiesta. I tag abilitano osservazione, cancellazione e debugging attraverso le catene di lavoro.
- Testare i worker in isolamento con
TestListenableWorkerBuilder, poi testare le catene con simulazione di constraints tramiteTestDriver. - L'API
WorkMetricsInfodi WorkManager 2.12 fornisce lo storico delle esecuzioni. Va usata per debuggare pattern di retry e motivi di stop in produzione. - I constraints risparmiano batteria. Un worker che fallisce per mancanza di rete spreca cicli CPU e consuma energia nei tentativi successivi.
- Il minimo di 15 minuti per il lavoro periodico è imposto da Android, non da WorkManager. I task periodici vanno progettati per tollerare questo intervallo.
Sapresti trovare il bug in Android?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 22 agosto 2026
Tag
Condividi
Articoli correlati

Jetpack Navigation Compose nel 2026: Navigazione Type-Safe e Domande da Colloquio
Una guida completa alla navigazione type-safe con Jetpack Compose. Esempi pratici, best practice e domande frequenti nei colloqui tecnici per sviluppatori Android.

Kotlin Flow vs StateFlow vs SharedFlow: domande da colloquio Android nel 2026
Le domande su Kotlin Flow vs StateFlow vs SharedFlow che gli intervistatori Android pongono nel 2026, con risposte chiare, una tabella comparativa e codice pronto per la produzione.

Android 16 nel 2026: Nuove API, Modalità Desktop e Domande per i Colloqui
Android 16 porta cambiamenti radicali per gli sviluppatori: rendering edge-to-edge obbligatorio, modalità desktop, notifiche ProgressStyle e navigazione predittiva back. Un deep-dive tecnico con esempi di codice e domande per i colloqui.