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:

  1. O usuário iniciou explicitamente esta tarefa e espera que ela continue? (tocou em “Gravar”, “Iniciar rota”, “Play”)
  2. A tarefa é contínua e sensível a interrupção? (parar no meio quebra o resultado)
  3. O usuário precisa ver que ela está acontecendo?

Três “sim” → foreground service. Qualquer “não” → provavelmente existe algo melhor.

CenárioEscolha adequada
Player de áudio/vídeo em backgroundForeground service (mediaPlayback) com Media3
Navegação turn-by-turnForeground service (location)
Gravação de áudio/vídeoForeground service (microphone / camera)
Upload grande com progresso visívelForeground service curto ou WorkManager com setForeground()
Sincronizar dados quando houver redeWorkManager com constraints
Reagir a evento do servidorFCM + trabalho curto
Tarefa em horário exatoAlarmManager
Trabalho ligado à tela abertaCoroutine 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_SERVICE sozinha 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 location sem ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION aprovada lança exceção no startForeground(). Trate isso com o mesmo cuidado descrito no guia de permissões no Android.
  • dataSync nã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 em dataSync, 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_STICKY não é garantia. O sistema pode recriar o service com intent nulo; trate esse caminho ou prefira START_NOT_STICKY quando 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_NOTIFICATIONS no 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 é:

  1. exibir uma notificação com ação (“Retomar download”);
  2. iniciar o service quando o usuário tocar;
  3. 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: dumpsys de 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:

  1. startForeground() acontece nos primeiros segundos em todos os caminhos de start;
  2. foregroundServiceType do manifesto bate com o passado em código;
  3. permissões de runtime do tipo estão concedidas antes do start;
  4. iniciar com o app em background falha de forma controlada, sem crash;
  5. rotação e reinício do processo não duplicam o service;
  6. ação “Parar” na notificação encerra e remove tudo;
  7. bateria e memória estáveis em sessão longa;
  8. 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 (dataSync para tudo) — risco direto de rejeição na Play.
  • Chamar startForeground() tarde — o sistema mata o processo com ANR/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_STICKY garante 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:

  1. revisar o modelo de trabalho adiável em WorkManager;
  2. alinhar o momento de pedir permissões, inclusive POST_NOTIFICATIONS;
  3. tratar o sinal do servidor com FCM em vez de service eterno;
  4. medir impacto com StrictMode, JankStats e o guia de performance;
  5. garantir que a fila local segue offline-first e não depende do service estar vivo.