Capítulo 17: Tarefas em Segundo Plano com WorkManager e CoroutineWorker
Capítulo 17: Tarefas em Segundo Plano com WorkManager e CoroutineWorker
Especialização em Mobile Nativo com Android • Kotlin 2.0 & Jetpack Compose
🗺️ Mapa Conceitual do Agendamento Resiliente com WorkManager
flowchart TD
APP["App / ViewModel (Dispara Agendamento)"] --> REQ["WorkRequest (OneTime ou PeriodicWorkRequest)"]
subgraph RESTRICTIONS["Definição de Restrições (Constraints)"]
REQ --> C1["NetworkType.CONNECTED / UNMETERED (Apenas Wi-Fi)"]
REQ --> C2["setRequiresBatteryNotLow(true)"]
REQ --> C3["setRequiresDeviceIdle(true)"]
end
subgraph WORK_MANAGER["Motor do WorkManager (AndroidX Architecture)"]
RESTRICTIONS --> ENQUEUE["WorkManager.enqueueUniqueWork()"]
ENQUEUE --> ROOM_INTERN["SQLite Interno do WorkManager (Garante Persistência)"]
ROOM_INTERN --> JOB_SCHED["Android JobScheduler / AlarmManager (Compatibilidade Multi-API)"]
end
subgraph EXECUTION["Execução Segura no SO (Doze Mode Compliant)"]
JOB_SCHED --> TRIGGER["Gatilho: Todas as Constraints são satisfeitas pelo SO"]
TRIGGER --> WORKER["CoroutineWorker (doWork() com Suspensão Nativa)"]
WORKER --> RESULT["Result.success() / Result.retry() / Result.failure()"]
end
RESULT -->|"Em caso de Retry"| BACKOFF["Exponential Backoff (Paupa Bateria)"]
BACKOFF --> TRIGGER
style APP fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style REQ fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style WORK_MANAGER fill:#b3e5fc,stroke:#0288d1,stroke-width:2px
style WORKER fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style BACKOFF fill:#fce4ec,stroke:#e91e63,stroke-width:2px
🏛️ 1. O Desafio do Segundo Plano e a Política de Bateria do Android
Desde o Android 6.0 (Marshmallow), o sistema operacional introduziu mecanismos rigorosos de economia de energia conhecidos como Doze Mode e App Standby Buckets. Esses mecanismos desligam conexões de rádio (Wi-Fi/4G) e suspendem a CPU quando o aparelho repousa sobre uma mesa ou fica com a tela desligada. Tentar executar serviços contínuos em segundo plano (Service em background) resulta no término forçado do processo pelo sistema.
O Jetpack WorkManager é a solução oficial definitiva para processamento em segundo plano adiável e garantido:
- Garantia de Execução Pós-Reboot: O WorkManager salva as solicitações de trabalho em um banco de dados SQLite interno antes de repassar ao agendador nativo do sistema (
JobScheduler). Mesmo que a bateria do celular acabe ou o usuário reinicie o aparelho, o trabalho continua registrado e será executado assim que o dispositivo for ligado novamente. - Execução Condicional Inteligente (Constraints): O desenvolvedor pode especificar que uma rotina pesada (como upload de backup ou sincronização de banco de dados) só seja iniciada se o celular estiver conectado a uma rede Wi-Fi não-medida, conectado ao carregador e com a bateria acima do nível crítico.
- Integração com Coroutines via
CoroutineWorker: A classe baseCoroutineWorkerexecuta o métododoWork()em um contexto de suspensão nativo gerenciado emDispatchers.DefaultouDispatchers.IO, garantindo cancelamento cooperativo e suporte aResult.retry()com algoritmo de recuo exponencial (Exponential Backoff).
📊 2. Comparativo de Mecanismos de Segundo Plano no Android
| Mecanismo | Garantido Pós-Reboot? | Respeita Doze Mode? | Caso de Uso Canônico |
|---|---|---|---|
Kotlin Coroutines (viewModelScope) |
❌ Não | Não se aplica | Chamadas imediatas enquanto a tela do usuário está visível |
| Foreground Service (com Notificação Fixa) | ⚠️ Parcial | Sim | Rastreamento GPS contínuo ou reprodução de áudio ativo |
| AlarmManager Exato | ❌ Não (sem reset) | Quebra o Doze Mode | Despertadores e alarmes que exigem disparo no milissegundo exato |
WorkManager (com CoroutineWorker) |
✅ Sim | ✅ Sim | Sincronização offline, uploads em lote, relatórios e auditorias |
💻 3. Laboratório Prático: Worker de Sincronização com Constraints e Backoff
O código a seguir apresenta a criação de um CoroutineWorker para sincronização periódica de logs com tratamento de retry resiliente:
package br.com.portal.mobile.android.cap17
import android.content.Context
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import kotlinx.coroutines.delay
import java.util.concurrent.TimeUnit
// 1. Worker com Suspensão Nativa
class SincronizacaoLogsWorker(
appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
return try {
// Simula leitura de pendências locais e upload HTTP
delay(1500)
val registrosEnviados = 120
val dadosSaida = workDataOf("TOTAL_ENVIADO" to registrosEnviados)
Result.success(dadosSaida)
} catch (e: Exception) {
// Retorna retry com Exponential Backoff em caso de falha de conexão
Result.retry()
}
}
}
// 2. Agendamento com Restrições no WorkManager
fun agendarSincronizacaoLogs(context: Context) {
// Restrições de Hardware e Conectividade
val restricoes = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // Apenas em rede Wi-Fi gratuita
.setRequiresBatteryNotLow(true) // Impede execução se bateria estiver baixa
.build()
// Requisição periódica a cada 6 horas com política de recuo exponencial
val requisicaoTrabalho = PeriodicWorkRequestBuilder<SincronizacaoLogsWorker>(
repeatInterval = 6,
repeatIntervalTimeUnit = TimeUnit.HOURS
)
.setConstraints(restricoes)
.setBackoffCriteria(
backoffPolicy = BackoffPolicy.EXPONENTIAL,
backoffDelay = 15,
timeUnit = TimeUnit.MINUTES
)
.build()
// Garante que exista apenas uma instância desse trabalho agendada (UniqueWork)
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"sincronizacao_periodica_logs",
ExistingPeriodicWorkPolicy.KEEP,
requisicaoTrabalho
)
}
⚠️ 4. Armadilhas Comuns no Uso do WorkManager
- Agendar Intervalos Menores que 15 Minutos: O Android impõe um limite rígido de segurança para o
PeriodicWorkRequest: o intervalo mínimo entre repetições é de 15 minutos. Tentar definir 5 minutos fará o WorkManager ignorar silenciosamente e arredondar para 15 minutos. - Passar Objetos Grandes no
Datade Entrada/Saída: O objetoDatado WorkManager possui um limite rígido de tamanho de 10 KB. Tentar trafegar bitmaps, áudios ou listas pesadas de JSON dispara umaIllegalStateException. Salve os arquivos em disco ou no Room e passe apenas o caminho (URI) ou identificador (ID). - Esquecer de Usar
ExistingWorkPolicy: Chamarenqueue()repetidamente sem uma política única (comoKEEPouREPLACE) pode criar centenas de workers concorrentes repetidos toda vez que o usuário abrir o app, esgotando recursos do aparelho.