Resposta rápida: um foreground service é a forma oficial de manter um trabalho contínuo, visível e iniciado pelo usuário rodando quando o app não está em primeiro plano — navegação turn-by-turn, player de mídia, gravação, chamada, upload longo com progresso. Em Kotlin, o caminho é: declarar o service no manifesto com um foregroundServiceType honesto, pedir a permissão específica daquele tipo (FOREGROUND_SERVICE_*), chamar ServiceCompat.startForeground() em poucos segundos com uma notificação de canal apropriado, e encerrar assim que a tarefa acabar. Se o trabalho é adiável (sync, upload sem urgência, limpeza), não use foreground service: use WorkManager. Se o trabalho é apenas reagir a um evento do servidor, use FCM. Foreground service é a exceção justificada, não o padrão.
Poucas APIs Android geram tanto retrabalho quanto essa. O time coloca um service “para não morrer”, o Play Console pede declaração de tipo, o Android 14+ recusa o startForeground() sem permissão coerente, o app leva ForegroundServiceStartNotAllowedException ao tentar iniciar em background, e a bateria vira reclamação na review. Este guia fecha a lacuna: quando o foreground service é a escolha certa, como implementá-lo em Kotlin sem gambiarra, e quais alternativas resolvem 80% dos casos que aparecem em code review.
Antes de fixar versões de biblioteca ou tipos de service, confira a documentação oficial de foreground services e a política de foreground service do Google Play. As regras mudam a cada release do Android — o raciocínio abaixo é estável, a lista de tipos não.
Quando um foreground service é realmente necessário
Faça as três perguntas na ordem:
- O usuário iniciou explicitamente esta tarefa e espera que ela continue? (tocou em “Gravar”, “Iniciar rota”, “Play”)
- A tarefa é contínua e sensível a interrupção? (parar no meio quebra o resultado)
- O usuário precisa ver que ela está acontecendo?
Três “sim” → foreground service. Qualquer “não” → provavelmente existe algo melhor.
| Cenário | Escolha adequada |
|---|---|
| Player de áudio/vídeo em background | Foreground service (mediaPlayback) com Media3 |
| Navegação turn-by-turn | Foreground service (location) |
| Gravação de áudio/vídeo | Foreground service (microphone / camera) |
| Upload grande com progresso visível | Foreground service curto ou WorkManager com setForeground() |
| Sincronizar dados quando houver rede | WorkManager com constraints |
| Reagir a evento do servidor | FCM + trabalho curto |
| Tarefa em horário exato | AlarmManager |
| Trabalho ligado à tela aberta | Coroutine no ViewModel |
O antipadrão mais comum em apps brasileiros de campo (logística, vistoria, delivery) é usar foreground service permanente só para “não perder dados”. Na prática, offline-first com fila local + WorkManager entrega o mesmo resultado sem notificação eterna e sem risco de política.
Manifesto: tipo, permissões e honestidade
O manifesto declara o que o service faz. Declarar tipo errado é motivo de rejeição na Play Store.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<service
android:name=".rota.RotaService"
android:exported="false"
android:foregroundServiceType="location" />
</application>
</manifest>
Regras que economizam horas de depuração:
FOREGROUND_SERVICEsozinha não basta desde o Android 14: cada tipo exige sua permissão irmã (FOREGROUND_SERVICE_LOCATION,FOREGROUND_SERVICE_MEDIA_PLAYBACK,FOREGROUND_SERVICE_CAMERA,FOREGROUND_SERVICE_MICROPHONE,FOREGROUND_SERVICE_DATA_SYNC, entre outros).- O tipo precisa ter a permissão de runtime correspondente concedida. Um service
locationsemACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATIONaprovada lança exceção nostartForeground(). Trate isso com o mesmo cuidado descrito no guia de permissões no Android. dataSyncnão é curinga. É o tipo com escrutínio mais alto na revisão da Play e com limites de tempo mais duros em versões recentes. Se o seu caso cabe emdataSync, quase sempre cabe melhor em WorkManager.exported="false"salvo necessidade real de outro app iniciar o service.
Implementação em Kotlin
Um foreground service enxuto: recebe a intenção, sobe a notificação imediatamente, delega o trabalho para coroutines e se encerra sozinho.
class RotaService : Service() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_PARAR -> {
pararTudo()
return START_NOT_STICKY
}
}
// Precisa acontecer nos primeiros segundos após o start.
ServiceCompat.startForeground(
this,
NOTIFICACAO_ID,
construirNotificacao(progresso = null),
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION,
)
scope.launch {
try {
acompanharRota().collect { estado ->
atualizarNotificacao(estado)
}
} finally {
withContext(Dispatchers.Main) { pararTudo() }
}
}
return START_STICKY
}
private fun pararTudo() {
scope.cancel()
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf()
}
override fun onDestroy() {
scope.cancel()
super.onDestroy()
}
companion object {
const val NOTIFICACAO_ID = 4201
const val ACTION_PARAR = "br.dev.kotlin.PARAR_ROTA"
}
}
Pontos que costumam ser cobrados em code review:
- Nunca bloqueie a thread principal dentro do service.
onStartCommand()roda na main thread; todo I/O vai para coroutines com dispatcher adequado (veja coroutines em Kotlin e Flow). - Cancele o escopo em
onDestroy()e ao parar — service vazado é fonte clássica de leak detectável com LeakCanary. START_STICKYnão é garantia. O sistema pode recriar o service comintentnulo; trate esse caminho ou prefiraSTART_NOT_STICKYquando reiniciar sem contexto não faz sentido.- Ofereça uma ação de parar na própria notificação. Usuário sem botão de saída vira desinstalação.
A notificação obrigatória — e como não ser silenciado
A notificação do foreground service não é enfeite: é o contrato com o usuário.
private fun construirNotificacao(progresso: Int?): Notification {
val pararIntent = PendingIntent.getService(
this,
0,
Intent(this, RotaService::class.java).setAction(ACTION_PARAR),
PendingIntent.FLAG_IMMUTABLE,
)
return NotificationCompat.Builder(this, CANAL_ROTA)
.setContentTitle("Rota em andamento")
.setContentText(progresso?.let { "Progresso: $it%" } ?: "Preparando rota")
.setSmallIcon(R.drawable.ic_rota)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setCategory(NotificationCompat.CATEGORY_NAVIGATION)
.addAction(R.drawable.ic_stop, "Parar", pararIntent)
.setContentIntent(abrirTelaDaRota())
.build()
}
Boas práticas:
- Canal próprio, de importância baixa/média, separado do canal de marketing. O usuário deve poder silenciar promoções sem matar a visibilidade da tarefa.
setOnlyAlertOnce(true)para atualizações de progresso não vibrarem a cada segundo.- Atualize com parcimônia — atualizar notificação em loop apertado custa bateria e aparece em profiling (Android Studio Profiler).
POST_NOTIFICATIONSno Android 13+ afeta a exibição; o service ainda roda, mas o usuário perde a pista visual. Peça a permissão no contexto certo.- Nada de dado sensível no texto visível na tela de bloqueio — vale a mesma disciplina de LGPD em apps Android.
Limites de início em background
Esta é a origem da maioria dos crashes do tipo ForegroundServiceStartNotAllowedException: desde o Android 12, um app em background não pode simplesmente iniciar um foreground service. Existem exceções (o app está visível, o usuário acabou de interagir, há uma notificação de alta prioridade com ação do usuário, o dispositivo recebeu um exact alarm etc.), mas a regra prática é:
Inicie o foreground service a partir de uma ação clara do usuário, com o app visível — ou não o inicie.
Se você recebeu um push e quer subir um service, pense duas vezes. Na maioria dos casos o correto é:
- exibir uma notificação com ação (“Retomar download”);
- iniciar o service quando o usuário tocar;
- ou enfileirar o trabalho em WorkManager, que sabe negociar com o sistema.
Ainda assim, blinde o código:
fun iniciarRotaComSeguranca(context: Context) {
val intent = Intent(context, RotaService::class.java)
try {
ContextCompat.startForegroundService(context, intent)
} catch (e: Exception) {
// Android 12+: pode falhar se o app não estiver em estado permitido.
agendarFallbackComWorkManager(context)
}
}
Registre a falha na sua telemetria de estabilidade (Crashlytics e ANR) para saber com que frequência o caminho de exceção acontece em produção — normalmente é bem mais do que o time imagina.
WorkManager com setForeground(): o meio-termo
Quando o trabalho é longo, tem progresso e o usuário iniciou, mas você quer as garantias de retry e constraints do WorkManager, use CoroutineWorker com setForeground():
class UploadWorker(
context: Context,
params: WorkerParameters,
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
setForeground(criarForegroundInfo(progresso = 0))
return try {
enviarArquivos { progresso ->
setForeground(criarForegroundInfo(progresso))
}
Result.success()
} catch (e: IOException) {
Result.retry()
}
}
private fun criarForegroundInfo(progresso: Int) = ForegroundInfo(
UPLOAD_ID,
construirNotificacaoUpload(progresso),
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
}
Vantagem: você herda backoff, constraints de rede/bateria e observabilidade do WorkManager, mas mostra progresso enquanto o trabalho está ativo. Desvantagem: continua sujeito às políticas de dataSync. Para uploads que podem esperar, remova o foreground e deixe o WorkManager decidir a janela.
Bateria, Doze e a realidade dos OEMs
No Brasil, boa parte da base usa aparelhos com gerenciadores de bateria agressivos além do Doze do Android puro. Consequências práticas:
- Um foreground service pode ser encerrado por gerenciadores agressivos, mesmo sendo legítimo. Projete para retomar, não para assumir continuidade.
- Persista o estado a cada marco (não a cada tick): o usuário reabre o app e a tarefa continua de onde parou. Uma fila local resolve isso melhor do que memória do service.
- Não peça isenção de otimização de bateria como padrão. Pedir para todo mundo é sinal de arquitetura errada e queima confiança.
- Meça o custo real:
dumpsysde bateria, StrictMode para I/O indevido na main thread e JankStats para o impacto na UI enquanto o service roda.
Testes e verificação antes do release
Checklist mínimo:
startForeground()acontece nos primeiros segundos em todos os caminhos de start;foregroundServiceTypedo manifesto bate com o passado em código;- permissões de runtime do tipo estão concedidas antes do start;
- iniciar com o app em background falha de forma controlada, sem crash;
- rotação e reinício do processo não duplicam o service;
- ação “Parar” na notificação encerra e remove tudo;
- bateria e memória estáveis em sessão longa;
- sem leaks após o
stopSelf().
Para o fluxo do usuário (iniciar, ver notificação, voltar à tela correta), testes instrumentados com Espresso cobrem a navegação; para a lógica interna, extraia o trabalho para classes puras testáveis e deixe o service como casca fina — o mesmo princípio de modularização Android.
Erros comuns
- Service permanente “por segurança” — a solução real é fila local + WorkManager.
- Tipo de service inflado (
dataSyncpara tudo) — risco direto de rejeição na Play. - Chamar
startForeground()tarde — o sistema mata o processo comANR/crash. - Notificação sem ação de parar — o usuário desinstala em vez de pausar.
- Escopo de coroutine não cancelado — leak silencioso e trabalho zumbi.
- Assumir que
START_STICKYgarante ressurreição — não garante em todos os OEMs. - Iniciar service a partir de push sem interação — exceção em Android 12+.
- Ignorar
POST_NOTIFICATIONS— tarefa invisível vira reclamação de “app travado”.
Onde isso pesa na carreira Android
Foreground service é assunto de entrevista sênior justamente porque exige julgamento, não decoreba: você precisa argumentar por que não usou, quando usou, e como o produto se comporta quando o sistema encerra o processo. Vale colocar no portfólio um app com um fluxo real (gravação, upload com progresso ou player) documentando tipo escolhido, permissões e estratégia de retomada. Combine com o roadmap Android e acompanhe as vagas Kotlin — descrições de vaga de Android sênior citam ForegroundService ao lado de WorkManager e Coroutines com frequência.
Perguntas frequentes
Foreground service impede o Android de matar meu app?
Não. Ele reduz muito a probabilidade e dá prioridade maior ao processo, mas em pressão de memória extrema ou com gerenciadores agressivos de OEM o processo ainda pode cair. Projete para retomar estado, não para nunca morrer.
Posso usar foreground service para sincronizar dados a cada 15 minutos?
Esse é exatamente o caso do WorkManager com trabalho periódico e constraints. Um foreground service permanente para sync periódico tende a ser reprovado na revisão da Play e queima bateria sem necessidade.
Qual a diferença entre foreground service e WorkManager com setForeground()?
O service é você gerenciando ciclo de vida diretamente. O CoroutineWorker com setForeground() mostra a mesma notificação, mas roda dentro do WorkManager, herdando retry, constraints e observabilidade. Para trabalho iniciado pelo usuário com fim previsível, o worker costuma ser o caminho mais barato de manter.
Preciso mesmo declarar foregroundServiceType?
Sim para versões recentes do Android, e a declaração precisa ser verdadeira e coerente com as permissões concedidas. Declaração genérica ou incorreta gera exceção em runtime e problema de política na loja.
Como testar o comportamento em background restrito?
Use as opções de desenvolvedor para limitar processos em background, force Doze via adb shell dumpsys deviceidle force-idle, e teste em pelo menos um aparelho com gerenciador agressivo de bateria além do emulador. O comportamento no Pixel não representa a base brasileira inteira.
Conclusão e próximos passos
Foreground service é uma ferramenta cirúrgica: excelente para trabalho contínuo, visível e iniciado pelo usuário; péssima como muleta contra as regras de background do Android. O caminho saudável em Kotlin é service fino, notificação honesta com ação de parar, tipo declarado com precisão, coroutines canceláveis e estado persistido para retomada.
Próximos passos no cluster Android deste site:
- revisar o modelo de trabalho adiável em WorkManager;
- alinhar o momento de pedir permissões, inclusive
POST_NOTIFICATIONS; - tratar o sinal do servidor com FCM em vez de service eterno;
- medir impacto com StrictMode, JankStats e o guia de performance;
- garantir que a fila local segue offline-first e não depende do service estar vivo.