¿Cuándo usar AlarmManager?
AlarmManager está diseñado para un caso de uso muy específico: ejecutar código en un momento exacto en el tiempo. A diferencia de WorkManager (que puede diferir el trabajo), AlarmManager dispara en el tiempo pedido incluso durante Doze.
Casos de uso legítimos:
- Alarma de despertador — debe sonar a las 7:00 AM exactas
- Recordatorio de calendario — notificar 10 minutos antes de una reunión
- Timer de cocina — alertar cuando pasan 30 minutos exactos
- Medicación — recordatorio a hora fija
No uses AlarmManager para sincronización periódicaSi lo que necesitás es "sincronizar datos cada hora aproximadamente", WorkManager es la herramienta correcta. AlarmManager consume más batería y tiene restricciones severas en versiones modernas de Android. El uso incorrecto de alarmas exactas es causal de rechazo en Play Store.
Tipos de alarma
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
// RTC_WAKEUP — hora real del reloj, despierta el dispositivo en Doze
// RTC — hora real del reloj, NO despierta en Doze (diferida)
// ELAPSED_REALTIME_WAKEUP — tiempo desde el boot, despierta en Doze
// ELAPSED_REALTIME — tiempo desde el boot, NO despierta en Doze
// set() — inexacta, el sistema puede agrupar alarmas para ahorrar batería
alarmManager.set(
AlarmManager.RTC_WAKEUP,
tiempoEnMillis,
pendingIntent
)
// setExact() — exacta pero puede ser diferida en Doze
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
tiempoEnMillis,
pendingIntent
)
// setExactAndAllowWhileIdle() — exacta Y dispara durante Doze
// Usar para alarmas que el usuario configuró explícitamente
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
tiempoEnMillis,
pendingIntent
)
// setAlarmClock() — la más exacta, para alarmas de despertador
// Muestra un ícono en la barra de estado
val alarmInfo = AlarmManager.AlarmClockInfo(tiempoEnMillis, pendingIntentMostrarApp)
alarmManager.setAlarmClock(alarmInfo, pendingIntentDisparo)
Alarmas exactas — permiso en Android 12+
Desde Android 12 (API 31), las alarmas exactas requieren un permiso especial que el usuario debe conceder manualmente:
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!-- O desde Android 13, el permiso más restrictivo: -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<!-- USE_EXACT_ALARM solo para alarmas, calendarios y apps de timer -->
// Verificar si el permiso fue concedido
fun puedeUsarAlarmasExactas(): Boolean {
val alarmManager = getSystemService(AlarmManager::class.java)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
alarmManager.canScheduleExactAlarms()
} else {
true // En versiones anteriores siempre se puede
}
}
// Si no tiene el permiso, llevar al usuario a la configuración
fun pedirPermisoAlarmas() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
data = Uri.parse("package:$packageName")
}
startActivity(intent)
}
}
BroadcastReceiver — el receptor de la alarma
// El receiver que se ejecuta cuando dispara la alarma
class AlarmaReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val alarmaId = intent.getIntExtra("alarma_id", -1)
val mensaje = intent.getStringExtra("mensaje") ?: "Alarma"
// Mostrar notificación
mostrarNotificacionAlarma(context, alarmaId, mensaje)
// O iniciar un servicio si necesitás hacer trabajo más largo
// val serviceIntent = Intent(context, AlarmaService::class.java)
// context.startForegroundService(serviceIntent)
}
private fun mostrarNotificacionAlarma(context: Context, id: Int, mensaje: String) {
val notification = NotificationCompat.Builder(context, "alarmas")
.setContentTitle("Recordatorio")
.setContentText(mensaje)
.setSmallIcon(R.drawable.ic_alarm)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.build()
val manager = context.getSystemService(NotificationManager::class.java)
manager.notify(id, notification)
}
}
// Registrar en el Manifest:
// <receiver android:name=".AlarmaReceiver" android:exported="false" />
Ejemplo completo: alarma de recordatorio
class AlarmaManager(private val context: Context) {
private val alarmManager = context.getSystemService(AlarmManager::class.java)
fun programarRecordatorio(id: Int, mensaje: String, tiempoMs: Long) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!alarmManager.canScheduleExactAlarms()) {
// Pedir permiso al usuario
return
}
}
val intent = Intent(context, AlarmaReceiver::class.java).apply {
putExtra("alarma_id", id)
putExtra("mensaje", mensaje)
}
val pendingIntent = PendingIntent.getBroadcast(
context,
id, // request code único por alarma
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
tiempoMs,
pendingIntent
)
}
}
Cancelar alarmas
fun cancelarAlarma(id: Int) {
val intent = Intent(context, AlarmaReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context, id, intent,
PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
)
pendingIntent?.let {
alarmManager.cancel(it)
it.cancel()
}
}
Sobrevivir al reinicio del dispositivo
Las alarmas se pierden cuando el dispositivo se reinicia. Para restaurarlas, registrá un receiver para el broadcast de boot:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
android:name=".BootReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
// Leer las alarmas guardadas en Room/DataStore y reprogramarlas
val alarmaManager = AlarmaManager(context)
val alarmasGuardadas = AlarmaDatabase.getInstance(context).alarmaDao().getAll()
alarmasGuardadas.forEach { alarma ->
if (alarma.tiempoMs > System.currentTimeMillis()) {
alarmaManager.programarRecordatorio(
alarma.id, alarma.mensaje, alarma.tiempoMs
)
}
}
}
}
}