---
title: "AlarmManager e Alarmes Exatos no Android com Kotlin em 2026"
url: "https://kotlin.dev.br/blog/alarmmanager-exact-alarms-android-kotlin-2026/"
markdown_url: "https://kotlin.dev.br/blog/alarmmanager-exact-alarms-android-kotlin-2026.MD"
description: "Aprenda a usar AlarmManager com alarmes exatos em Kotlin: SCHEDULE_EXACT_ALARM, canScheduleExactAlarms, setExactAndAllowWhileIdle e alternativas ao alarme de precisão."
date: "2026-09-17"
author: "Karina Melo"
---

# AlarmManager e Alarmes Exatos no Android com Kotlin em 2026

Aprenda a usar AlarmManager com alarmes exatos em Kotlin: SCHEDULE_EXACT_ALARM, canScheduleExactAlarms, setExactAndAllowWhileIdle e alternativas ao alarme de precisão.


**Resposta rápida:** para executar código em um horário **exato** no Android — despertador, lembrete de remédio, alarme de pomodoro, fechamento de pedido — use `AlarmManager` com `setExactAndAllowWhileIdle()` (ou `setAlarmClock()` para alarmes de usuário). Desde o Android 12 (API 31), alarmes exatos exigem a permissão `SCHEDULE_EXACT_ALARM` declarada no manifest e aprovada na Play Store, e o app precisa checar `canScheduleExactAlarms()` em runtime. Para quase todo o resto — sincronização, retry, upload de foto, trabalho periódico — o certo é [WorkManager](/blog/workmanager-kotlin-android-2026/), que foi feito para trabalho adiado e não precisa de horário exato.

Alarme exato é a exceção, não a regra. O sistema tenta economizar bateria agrupando tarefas (Doze, App Standby), e cada alarme exato fura esse otimização. Este guia mostra quando o alarme exato é justificável, como implementá-lo em Kotlin com `AlarmManager` e `BroadcastReceiver`, como tratar a permissão no Android 12+ e o que mudou no Android 14, e como migrar um fluxo que hoje abusa de alarme para WorkManager ou [foreground service](/blog/foreground-service-android-kotlin-2026/).

## Alarme exato vs. trabalho adiado: a decisão

Antes de escrever qualquer linha, responda: **o usuário percebe se o horário atrasar 5 minutos?**

| Necessidade | API recomendada | Alarme exato? |
|---|---|---|
| Despertador, timer de cozinha, pomodoro | `setAlarmClock()` ou `setExactAndAllowWhileIdle()` | Sim |
| Lembrete de medicação em horário marcado | `setExactAndAllowWhileIdle()` | Sim |
| Fechar expiração de pedido/cota em horário fixo | `setExactAndAllowWhileIdle()` | Sim |
| Sincronizar dados, retry, backup | [WorkManager](/blog/workmanager-kotlin-android-2026/) | Não |
| Trabalho periódico que pode atrasar | WorkManager (`PeriodicWorkRequest`) | Não |
| Ação disparada por evento do servidor | [FCM push](/blog/fcm-notificacoes-push-android-kotlin-compose-2026/) | Não |
| Tarefa longa visível ao usuário | [Foreground service](/blog/foreground-service-android-kotlin-2026/) | Depende |

Regra de bolso: **se ninguém percebe o atraso, não é alarme exato**. `setWindow()`, `setAndAllowWhileIdle()` e o WorkManager cobrem a vasta maioria dos casos com muito menos atrito de permissão e bateria.

## Permissões: o que mudou no Android 12, 13 e 14

O `AlarmManager` existe desde a API 1, mas o contrato mudou:

- **Android 12 (API 31):** alarmes exatos passaram a exigir `SCHEDULE_EXACT_ALARM` no manifest. Apps recém-instalados do zero (target 31+) **não** recebem a concessão automaticamente — ela fica desligada até o usuário ativar em *Configurações > Apps > Permissões especiais*.
- **Android 13 (API 33):** sem mudança relevante para alarmes; a onda foi de permissões de notificação (lembre-se de pedir `POST_NOTIFICATIONS` para o lembrete aparecer).
- **Android 14 (API 34):** apps direcionados à Play Store precisam **declarar um caso de uso justificado** no Play Console para usar `SCHEDULE_EXACT_ALARM`. A alternativa declarável sem justificativa é `USE_EXACT_ALARM`, reservada a categorias cuja função principal é alarme (despertador, calendário) — o Google aprova caso a caso.

No manifest:

```xml
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
    android:maxSdkVersion="32" />
<!-- Android 13+ usa a mesma SCHEDULE_EXACT_ALARM; USE_EXACT_ALARM só
     para apps cujo propósito central é alarme (ex.: despertador): -->
<!-- <uses-permission android:name="android.permission.USE_EXACT_ALARM" /> -->

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
```

`RECEIVE_BOOT_COMPLETED` entra porque alarmes são cancelados quando o aparelho reinicia — apps de alarme re-agendam no boot.

Em runtime, antes de agendar, cheque a concessão:

```kotlin
val alarmManager = context.getSystemService(AlarmManager::class.java)
if (alarmManager.canScheduleExactAlarms()) {
    alarmManager.setExactAndAllowWhileIdle(/* ... */)
} else {
    // leve o usuário até Ajustes ou caia para setWindow/WorkManager
    val intent = Intent(
        Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM,
        Uri.parse("package:${context.packageName}")
    )
    context.startActivity(intent)
}
```

Se o usuário revogar a permissão com o app aberto, o sistema mata o processo. Trate isso no próximo `onResume` revalidando `canScheduleExactAlarms()` — é o erro mais comum em produção.

## Implementação em Kotlin: receptor + PendingIntent

O fluxo tem três peças: o `BroadcastReceiver` que executa a ação, o `PendingIntent` que aponta para ele, e a chamada do `AlarmManager`. Como o alvo de um alarme é um receptor, nada de `ViewModel` ou composables aqui.

### 1. O receptor

```kotlin
class LembreteReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val id = intent.getLongExtra(EXTRA_LEMBRETE_ID, -1L)
        if (id == -1L) return

        // Roda na main thread: despache para uma coroutine scope do app
        val work = LembreteNotifier(context).mostrarLembrete(id)
        goAsync().use { pendingResult ->
            CoroutineScope(Dispatchers.Default).launch {
                try {
                    work.join()
                } finally {
                    pendingResult.finish()
                }
            }
        }
    }

    companion object {
        const val EXTRA_LEMBRETE_ID = "lembrete_id"

        fun pendingIntent(context: Context, id: Long): PendingIntent {
            val intent = Intent(context, LembreteReceiver::class.java)
                .putExtra(EXTRA_LEMBRETE_ID, id)
            return PendingIntent.getBroadcast(
                context,
                id.hashCode(),           // requestCode único por lembrete
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or
                    PendingIntent.FLAG_IMMUTABLE
            )
        }
    }
}
```

Duas armadilhas nesses 30 linhas: `FLAG_IMMUTABLE` é **obrigatório** desde o Android 12, e o `requestCode` precisa ser distinto por alarme — usar o mesmo código para dois lembretes faz o segundo `PendingIntent` sobrescrever o primeiro.

Registre no manifest (alarmes disparados por `AlarmManager` exigem registro explícito, não vale receiver dinâmico):

```xml
<receiver android:name=".LembreteReceiver" android:exported="false" />

<receiver android:name=".BootReceiver" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>
```

### 2. Agendando o alarme exato

```kotlin
fun agendarLembrete(
    context: Context,
    id: Long,
    quando: LocalDateTime,
) {
    val alarmManager = context.getSystemService(AlarmManager::class.java)
    val triggerAtMillis = quando.atZone(ZoneId.systemDefault())
        .toInstant().toEpochMilli()

    when {
        // alarme de usuário (despertador): aparece no relógio do sistema
        alarmManager.canScheduleExactAlarms() ->
            alarmManager.setExactAndAllowWhileIdle(
                AlarmManager.RTC_WAKEUP,
                triggerAtMillis,
                LembreteReceiver.pendingIntent(context, id)
            )
        else ->
            // sem concessão: melhor esforço, janela de 15 min
            alarmManager.setWindow(
                AlarmManager.RTC_WAKEUP,
                triggerAtMillis,
                TimeUnit.MINUTES.toMillis(15),
                LembreteReceiver.pendingIntent(context, id)
            )
    }
}
```

`RTC_WAKEUP` acorda o aparelho e usa o relógio de parede (o correto para horários que o usuário enxerga). `ELAPSED_REALTIME*` usa tempo desde o boot — prefira para timers internos, imunes a o usuário mudar a hora do aparelho.

### 3. Re-agendando após reboot

```kotlin
class BootReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action != Intent.ACTION_BOOT_COMPLETED) return
        val repositorio = LembreteRepository(context)
        val trabalho = CoroutineScope(Dispatchers.Default).launch {
            repositorio.proximosAtivos().forEach { lembrete ->
                agendarLembrete(context, lembrete.id, lembrete.quando)
            }
        }
        goAsync().use { pending ->
            CoroutineScope(Dispatchers.Default).launch {
                try { trabalho.join() } finally { pending.finish() }
            }
        }
    }
}
```

O armazenamento dos lembretes pendentes pode ser [Room](/tutoriais/kotlin-room-database-tutorial/) ou DataStore — o importante é que o app consiga reconstruir a agenda sozinho, porque o sistema não faz isso por você.

## Despertador de verdade: setAlarmClock()

Para despertador e timer de cozinha, `setAlarmClock()` é mais honesto que `setExactAndAllowWhileIdle()`: ele mostra o ícone de alarme na status bar, trata o caso "usuário mudou o fuso" e não depende da permissão especial:

```kotlin
alarmManager.setAlarmClock(
    AlarmManager.AlarmClockInfo(triggerAtMillis, mostrarIntent),
    LembreteReceiver.pendingIntent(context, id)
)
```

O `mostrarIntent` abre a tela do seu app quando o usuário toca no ícone de alarme da status bar. `setAlarmClock()` pode disparar até pouco antes do horário (o sistema antecipa para não atrasar) — aceitável para despertador, inaceitável para expiração de oferta, onde antecipar quebra a regra de negócio.

## Repetição: alarme que se re-agenda

O `setRepeating()` moderno **não é exato** — desde o Android 4.4 ele é *inexact* por design, e em Doze pode deslizar bastante. O padrão para alarme exato recorrente é re-agendar no próprio receptor:

```kotlin
override fun onReceive(context: Context, intent: Intent) {
    val proximo = calcularProximaOcorrencia() // ex.: +24h no fuso do usuário
    agendarLembrete(context, id, proximo)
    // ...executa a ação do alarme atual
}
```

Calcule sempre a próxima ocorrência a partir do horário *planejado*, não do horário *disparado* — senão um atraso de 2 minutos vira deriva permanente no alarme diário de 7h.

## Como testar

- **Adb:** force as condições com `adb shell dumpsys battery unplug`, `adb shell am set-standby-bucket <pkg> restricted` e confira `adb shell dumpsys alarm | grep <pkg>` para ver o alarme registrado.
- **Doze:** `adb shell su root am broadcast -a android.intent.action.DEVICE_IDLE_MODE_ENABLED` ou o comando de força de Doze nos aparelhos de teste.
- **Robolectric/E omit:** para o receptor, um teste de unidade valida a extração de extras e o cálculo da próxima ocorrência — o scheduling em si fica para testes instrumentados, junto com o que já se usa em [testes de UI no Android](/blog/testando-flow-stateflow-turbine-kotlin-2026/).
- **Revogação:** instale, agende, revogue a permissão especial em Ajustes e reabra o app — o fluxo de fallback para `setWindow`/WorkManager precisa sobreviver ao processo morto.

## Erros comuns

1. **Alarme exato para tudo.** Sincronização com `setExactAndAllowWhileIdle` a cada 15 minutos drena bateria e dá motivo para a Play Store negar a declaração de caso de uso. Use [WorkManager](/blog/workmanager-kotlin-android-2026/).
2. **Esquecer `FLAG_IMMUTABLE`.** Crash imediato no Android 12+ com `SecurityException`.
3. **`requestCode` repetido.** Dois `PendingIntent`s com o mesmo request code e o mesmo intent são o mesmo alarme — o primeiro some.
4. **Não checar `canScheduleExactAlarms()` em runtime.** A concessão pode ser revogada a qualquer momento; agendar exato sem ela lança `SecurityException` no Android 12+.
5. **Ignorar o reboot.** Alarmes não sobrevivem a reinício. Sem `BOOT_COMPLETED` + re-agendamento, o despertador falha exatamente no dia em que o usuário mais precisa.
6. **Trabalho longo no `onReceive`.** O receptor vive ~10 segundos. Notificação curta, ok; upload ou sincronização, nunca — despache para WorkManager ou foreground service.
7. **Mudança de fuso ignorada.** Guarde o horário como `LocalDateTime` + zona ou como instante absoluto, e recalcule na exibição. Viagem para outro fuso não deve disparar o remédio às 3h da manhã.

## Perguntas frequentes

**Preciso de permissão para alarmes não exatos?** Não. `set()`, `setWindow()` e `setRepeating()` funcionam sem qualquer permissão especial — pagam apenas com precisão.

**WorkManager não substitui AlarmManager?** Não para horário exato. WorkManager garante execução *eventual* com restrições (rede, bateria), sem promessa de horário. Para precisão de minuto, só AlarmManager exato.

**O que declarar no Play Console para passar na revisão do Android 14?** Um caso de uso da lista permitida (alarme, calendário, temporizador) com descrição concreta do comportamento visível ao usuário. "Sincronizar dados em horário exato" é recusa quase certa — e sinal de que a API certa é outra.

**E se o usuário negar a permissão especial?** Caia para `setWindow()` com a maior janela aceitável ou para WorkManager, e comunique o impacto ("seus lembretes podem atrasar até 15 minutos"). Não bloqueie o app.

## Conclusão

`AlarmManager` com alarme exato é uma ferramenta de nicho e poderosa: horário que o usuário percebe, permissão declarada e justificada, `canScheduleExactAlarms()` checado em runtime, re-agendamento no reboot e nada de trabalho longo no receptor. Para sincronização, retry e periódicos, o caminho continua sendo [WorkManager](/blog/workmanager-kotlin-android-2026/); para evento vindo do servidor, [notificação push com FCM](/blog/fcm-notificacoes-push-android-kotlin-compose-2026/); para execução longa visível, [foreground service](/blog/foreground-service-android-kotlin-2026/). Escolha a ferramenta pelo custo de atraso — e na dúvida, escolha a mais barata para a bateria do usuário.
