Cómo programar alarmas repetitivas

Las alarmas (basadas en la clase AlarmManager) ofrecen una manera de realizar operaciones programadas fuera del ciclo de vida de tu app. Por ejemplo, puedes usar una alarma a fin de comenzar una operación prolongada, como iniciar un servicio una vez al día para descargar el pronóstico del tiempo.

Las alarmas tienen las siguientes características:

  • Te permiten activar intents en horas o intervalos determinados.
  • Puedes usarlas junto con receptores de emisión a fin de iniciar servicios y realizar otras operaciones.
  • Funcionan fuera de tu app, de modo que puedes usarlas para activar eventos o acciones incluso cuando tu app no está en ejecución y el dispositivo está suspendido.
  • Te ayudan a minimizar los requisitos de recursos de tu app. Puedes programar operaciones sin depender de temporizadores o ejecutar servicios en segundo plano constantemente.

Nota: En el caso de las operaciones programadas que se garantiza que ocurrirán durante el ciclo de vida de tu app, en lugar de las alarmas, considera usar la clase Handler junto con Timer y Thread. Este enfoque le permite a Android tener un mejor control sobre los recursos del sistema.

Información sobre las desventajas

Una alarma repetitiva es un mecanismo relativamente simple con flexibilidad limitada. Es posible que no sea la mejor opción para tu app, en especial si necesitas activar operaciones de red. Una alarma mal diseñada puede agotar la batería y sobrecargar los servidores.

Una situación común para activar una operación fuera del ciclo de vida de tu app es la sincronización de datos con un servidor. Este es un caso en el que podría tentarte el uso de una alarma repetitiva. Sin embargo, si eres propietario del servidor que aloja los datos de tu app, usar Google Cloud Messaging (GCM) junto con un adaptador de sincronización es una mejor solución que el AlarmManager. Un adaptador de sincronización ofrece las mismas opciones de programación que el AlarmManager, pero brinda mayor flexibilidad. Por ejemplo, una sincronización podría basarse en un mensaje de "datos nuevos" del servidor o dispositivo (consulta Cómo ejecutar un adaptador de sincronización), la actividad (o inactividad) del usuario, el momento del día, etcétera. Consulta los videos de los vínculos que aparecen en la parte superior de esta página para obtener un análisis detallado de cómo y cuándo usar GCM y el adaptador de sincronización.

Las alarmas no se activan cuando el dispositivo está inactivo en el modo Descanso. Cualquier alarma programada se pospondrá hasta que el dispositivo salga del modo Descanso. Si necesitas asegurarte de que tus tareas se completen incluso cuando el dispositivo está inactivo, hay varias opciones disponibles. Puedes usar setAndAllowWhileIdle() o setExactAndAllowWhileIdle() a fin de garantizar que las alarmas se ejecuten. Otra opción es usar la nueva API de WorkManager, que se compiló a fin de ejecutar tareas en segundo plano, ya sea una única vez o con regularidad. Para obtener más información, consulta Cómo programar tareas con WorkManager.

Prácticas recomendadas

Cada decisión que tomes al diseñar tu alarma repetitiva puede afectar la manera en que tu app usa los recursos del sistema (o abusa de ellos). Por ejemplo, imagina una app popular que se sincroniza con un servidor. Si la operación de sincronización se basa en la hora del reloj y cada instancia de la app se sincroniza a las 11:00 p.m., la carga en el servidor puede generar una latencia alta o incluso "la denegación del servicio". Sigue estas prácticas recomendadas para usar las alarmas:

  • Agrega aleatorización (jitter) en cualquier solicitud de red que se active a causa de una alarma repetitiva:
    • Realiza las tareas locales cuando se active la alarma. Con "tareas locales" nos referimos a cualquier tarea que no interactúe con un servidor ni requiera datos de uno.
    • Al mismo tiempo, programa la alarma que contenga las solicitudes de red para que se active en algún momento aleatorio.
  • Mantén la frecuencia de la alarma al mínimo.
  • No actives el dispositivo innecesariamente (este comportamiento se determina por el tipo de alarma, como se describe en Cómo elegir un tipo de alarma).
  • No hagas que el tiempo de activación de tu alarma sea más preciso de lo necesario.

    Usa setInexactRepeating() en lugar de setRepeating(). Cuando usas setInexactRepeating(), Android sincroniza las alarmas repetitivas de varias apps y las activa a la vez. De esta manera, se reduce la cantidad total de veces que el sistema debe activar el dispositivo, lo que minimiza el consumo de la batería. A partir de Android 4.4 (API nivel 19), las alarmas repetitivas no son exactas. Ten en cuenta que, si bien setInexactRepeating() es mejor que setRepeating(), de igual manera puede sobrecargar el servidor si cada instancia de una app interactúa con él al mismo tiempo. Por lo tanto, en el caso de las solicitudes de red, agrega aleatorización a tus alarmas, como se explica más arriba.

  • Si es posible, evita basar tu alarma en la hora del reloj.

    Las alarmas repetitivas que se basan en una hora de activación precisa no se escalan bien. Si es posible, usa ELAPSED_REALTIME. En la siguiente sección, se describen con mayor detalle los diferentes tipos de alarmas.

Cómo establecer una alarma repetitiva

Como se describió anteriormente, las alarmas repetitivas son una buena opción para programar eventos comunes o búsquedas de datos. Una alarma repetitiva tiene las siguientes características:

  • Un tipo de alarma (Para obtener más información, consulta Cómo elegir un tipo de alarma)
  • Una hora de activación (Si la hora de activación que especificas es en el pasado, la alarma se activará inmediatamente)
  • El intervalo de la alarma (Por ejemplo, una vez al día, cada hora, cada 5 minutos, etcétera)
  • Un intent pendiente que se activa cuando se activa la alarma (Cuando configuras una segunda alarma que usa el mismo intent pendiente, esta reemplaza a la alarma original)

Para cancelar un PendingIntent, pasa FLAG_NO_CREATE a PendingIntent.getService() a fin de obtener una instancia del intent (si existe). AlarmManager.cancel():

Kotlin

    val alarmManager =
        context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
    val pendingIntent =
        PendingIntent.getService(context, requestId, intent,
                                    PendingIntent.FLAG_NO_CREATE)
    if (pendingIntent != null && alarmManager != null) {
      alarmManager.cancel(pendingIntent)
    }
    

Java

    AlarmManager alarmManager =
        (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    PendingIntent pendingIntent =
        PendingIntent.getService(context, requestId, intent,
                                    PendingIntent.FLAG_NO_CREATE);
    if (pendingIntent != null && alarmManager != null) {
      alarmManager.cancel(pendingIntent);
    }
    

Cómo elegir un tipo de alarma

Una de las primeras consideraciones al usar una alarma repetitiva es el tipo.

Hay dos tipos de relojes generales para las alarmas: "tiempo real transcurrido" y "reloj en tiempo real" (RTC). El tiempo real transcurrido usa el "tiempo desde que se inició el sistema" como referencia, y el reloj en tiempo real usa la hora UTC (reloj de pared). Por lo tanto, el tiempo real transcurrido es apropiado para establecer una alarma basada en el paso del tiempo (por ejemplo, una alarma que se activa cada 30 segundos), ya que esta no se ve afectada por la zona horaria ni por la configuración regional. El tipo de reloj en tiempo real funciona mejor para las alarmas que dependen de la configuración regional actual.

Ambos tipos tienen una versión de "activación", que indica que se debe activar la CPU del dispositivo si la pantalla está apagada. De esta manera, se garantiza que la alarma se active a la hora programada. Este enfoque resulta útil si tu app tiene una dependencia de tiempo, por ejemplo, si tiene un período limitado para ejecutar una operación específica. Si no usas la versión de activación de tu tipo de alarma, entonces todas las alarmas repetitivas se activarán cuando vuelva a activarse el dispositivo.

Si solo necesitas que tu alarma se active en un intervalo determinado (por ejemplo, cada media hora), usa uno de los tipos de tiempo real transcurrido. Por lo general, esta es la mejor opción.

Si necesitas que tu alarma se active en un momento determinado del día, entonces elige uno de los tipos de reloj en tiempo real basado en un reloj. Sin embargo, ten en cuenta que este enfoque puede tener algunas desventajas: es posible que la app no se traduzca bien a otras configuraciones regionales y, si el usuario cambia la configuración de hora del dispositivo, quizá cause que tu app se comporte de manera inesperada. Además, el uso de un tipo de alarma de reloj en tiempo real tampoco se escala bien, como se explicó anteriormente. Por lo tanto, te recomendamos que uses una alarma de "tiempo real transcurrido" si es posible.

A continuación, se muestra una lista de los tipos:

  • ELAPSED_REALTIME: Activa el intent pendiente en función de la cantidad de tiempo que transcurrió desde que se inició el dispositivo, pero no activa el dispositivo. El tiempo transcurrido incluye cualquier intervalo de tiempo durante el cual estuvo inactivo el dispositivo.
  • ELAPSED_REALTIME_WAKEUP: Despierta el dispositivo y activa el intent pendiente luego de que transcurrió la cantidad de tiempo especificada desde que se inició el dispositivo.
  • RTC: Activa el intent pendiente a la hora especificada, pero no activa el dispositivo.
  • RTC_WAKEUP: Activa el dispositivo a fin de activar el intent pendiente a la hora especificada.

Ejemplos de alarmas de tiempo real transcurrido

A continuación, se muestran algunos ejemplos del uso de ELAPSED_REALTIME_WAKEUP.

Activa el dispositivo para activar la alarma en 30 minutos, y cada 30 minutos después de la primera activación:

Kotlin

    // Hopefully your alarm will have a lower frequency than this!
    alarmMgr?.setInexactRepeating(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
            AlarmManager.INTERVAL_HALF_HOUR,
            alarmIntent
    )
    

Java

    // Hopefully your alarm will have a lower frequency than this!
    alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
            AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
    

Activa el dispositivo para activar una única alarma (no repetitiva) en un minuto:

Kotlin

    private var alarmMgr: AlarmManager? = null
    private lateinit var alarmIntent: PendingIntent
    ...
    alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
        PendingIntent.getBroadcast(context, 0, intent, 0)
    }

    alarmMgr?.set(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + 60 * 1000,
            alarmIntent
    )
    

Java

    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;
    ...
    alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, AlarmReceiver.class);
    alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

    alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() +
            60 * 1000, alarmIntent);
    

Ejemplos de alarmas de reloj en tiempo real

A continuación, se muestran algunos ejemplos del uso de RTC_WAKEUP.

Activa el dispositivo para activar la alarma aproximadamente a las 2:00 p.m. y que se repita una vez al día a la misma hora:

Kotlin

    // Set the alarm to start at approximately 2:00 p.m.
    val calendar: Calendar = Calendar.getInstance().apply {
        timeInMillis = System.currentTimeMillis()
        set(Calendar.HOUR_OF_DAY, 14)
    }

    // With setInexactRepeating(), you have to use one of the AlarmManager interval
    // constants--in this case, AlarmManager.INTERVAL_DAY.
    alarmMgr?.setInexactRepeating(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            AlarmManager.INTERVAL_DAY,
            alarmIntent
    )
    

Java

    // Set the alarm to start at approximately 2:00 p.m.
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.HOUR_OF_DAY, 14);

    // With setInexactRepeating(), you have to use one of the AlarmManager interval
    // constants--in this case, AlarmManager.INTERVAL_DAY.
    alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
            AlarmManager.INTERVAL_DAY, alarmIntent);
    

Activa el dispositivo para activar la alarma precisamente a las 8:30 a.m. y, desde entonces, cada 20 minutos:

Kotlin

    private var alarmMgr: AlarmManager? = null
    private lateinit var alarmIntent: PendingIntent
    ...
    alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
        PendingIntent.getBroadcast(context, 0, intent, 0)
    }

    // Set the alarm to start at 8:30 a.m.
    val calendar: Calendar = Calendar.getInstance().apply {
        timeInMillis = System.currentTimeMillis()
        set(Calendar.HOUR_OF_DAY, 8)
        set(Calendar.MINUTE, 30)
    }

    // setRepeating() lets you specify a precise custom interval--in this case,
    // 20 minutes.
    alarmMgr?.setRepeating(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            1000 * 60 * 20,
            alarmIntent
    )
    

Java

    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;
    ...
    alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, AlarmReceiver.class);
    alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

    // Set the alarm to start at 8:30 a.m.
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.HOUR_OF_DAY, 8);
    calendar.set(Calendar.MINUTE, 30);

    // setRepeating() lets you specify a precise custom interval--in this case,
    // 20 minutes.
    alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
            1000 * 60 * 20, alarmIntent);
    

Cómo decidir qué tan precisa debe ser la alarma

Como se describió anteriormente, seleccionar el tipo de alarma suele ser el primer paso para crear una. Otra distinción es qué tan precisa debe ser. Para la mayoría de las apps, setInexactRepeating() es la opción correcta. Cuando usas este método, Android sincroniza varias alarmas repetitivas que no son exactas y las activa al mismo tiempo. De esta manera, se reduce el consumo de batería.

En el caso de una app particular que tenga requisitos de tiempo rígidos (por ejemplo, la alarma necesita activarse precisamente a las 8:30 a.m. y, a partir de entonces, a cada hora en punto), usa setRepeating(). Sin embargo, deberías evitar el uso de alarmas exactas en lo posible.

Con setInexactRepeating(), no puedes especificar un intervalo personalizado como lo haces con setRepeating(). Debes usar una de las constantes de intervalo, como INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY, etcétera. Consulta AlarmManager para obtener la lista completa.

Cómo cancelar una alarma

Según tu app, es posible que quieras incluir la capacidad para cancelar la alarma. Si quieres cancelar una alarma, llama a cancel() en el Administrador de alarmas y pasa el PendingIntent que ya no quieras activar. Por ejemplo:

Kotlin

    // If the alarm has been set, cancel it.
    alarmMgr?.cancel(alarmIntent)
    

Java

    // If the alarm has been set, cancel it.
    if (alarmMgr!= null) {
        alarmMgr.cancel(alarmIntent);
    }
    

Cómo iniciar una alarma cuando se reinicia el dispositivo

De manera predeterminada, todas las alarmas se cancelan cuando se apaga un dispositivo. Si quieres evitarlo, puedes diseñar tu app para que reinicie automáticamente una alarma repetitiva en caso de que el usuario reinicie el dispositivo. De esta manera, se garantiza que el AlarmManager siga realizando las tareas sin que el usuario deba restablecer la alarma manualmente.

A continuación, se indican los pasos que debes seguir.

  1. Configura el permiso RECEIVE_BOOT_COMPLETED en el manifiesto de tu app. De esta manera, tu app podrá recibir el ACTION_BOOT_COMPLETED que se transmite una vez que el sistema finalizó el inicio (solo funciona si el usuario ya inició la app al menos una vez):
        <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  2. Implementa un BroadcastReceiver para recibir la transmisión:

    Kotlin

        class SampleBootReceiver : BroadcastReceiver() {
    
            override fun onReceive(context: Context, intent: Intent) {
                if (intent.action == "android.intent.action.BOOT_COMPLETED") {
                    // Set the alarm here.
                }
            }
        }
        

    Java

        public class SampleBootReceiver extends BroadcastReceiver {
    
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
                    // Set the alarm here.
                }
            }
        }
        
  3. Agrega el receptor al archivo de manifiesto de tu app con un filtro de intent que filtre la acción ACTION_BOOT_COMPLETED:
    <receiver android:name=".SampleBootReceiver"
                android:enabled="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            </intent-filter>
        </receiver>

    Ten en cuenta que, en el manifiesto, el receptor de inicio está configurado en android:enabled="false", lo que significa que no se lo llamará a menos que la app lo habilite explícitamente. De esta manera, evitas que se llame innecesariamente al receptor de inicio. Para habilitar un receptor (por ejemplo, si el usuario establece una alarma), haz lo siguiente:

    Kotlin

        val receiver = ComponentName(context, SampleBootReceiver::class.java)
    
        context.packageManager.setComponentEnabledSetting(
                receiver,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP
        )
        

    Java

        ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
        PackageManager pm = context.getPackageManager();
    
        pm.setComponentEnabledSetting(receiver,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);
        

    Una vez que habilites el receptor de esta manera, permanecerá habilitado aunque el usuario reinicie el dispositivo. En otras palabras, la habilitación programática del receptor anula la configuración del manifiesto, incluso luego de un reinicio. El receptor permanecerá habilitado hasta que tu app lo inhabilite. Para inhabilitar un receptor (por ejemplo, si el usuario cancela una alarma), haz lo siguiente:

    Kotlin

        val receiver = ComponentName(context, SampleBootReceiver::class.java)
    
        context.packageManager.setComponentEnabledSetting(
                receiver,
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP
        )
        

    Java

        ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
        PackageManager pm = context.getPackageManager();
    
        pm.setComponentEnabledSetting(receiver,
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);
        

Impactos de Descanso y App Standby

Las funciones Descanso y App Standby se introdujeron en Android 6.0 (API nivel 23) en un esfuerzo por extender la duración de la batería del dispositivo. Cuando el dispositivo esté en modo Descanso, las alarmas estándar se pospondrán hasta que el dispositivo salga de este modo o se abra un período de mantenimiento. Si debes activar una alarma incluso en el modo Descanso, puedes usar setAndAllowWhileIdle() o setExactAndAllowWhileIdle(). Tu app entrará en modo App Standby cuando esté inactiva, lo que significa que el usuario no la usó por un tiempo y esta no tiene ningún proceso en primer plano. Cuando la app se encuentra en modo App Standby, las alarmas se posponen al igual que en el modo Descanso. Esta restricción se anula cuando la app deja de estar inactiva o si el dispositivo se conecta a una fuente de alimentación. Para obtener más información sobre cómo estos modos afectan a tu app, consulta Cómo optimizar tu app para Descanso y App Standby.