Programar alarmes recorrentes

Os alarmes (com base na classe AlarmManager) oferecem uma maneira de realizar operações baseadas em tempo fora da vida útil do seu app. Você pode usar um alarme para iniciar uma operação de longa duração, por exemplo, para iniciar um serviço uma vez por dia para fazer o download de uma previsão do tempo.

Os alarmes têm estas características:

  • Permitem que você dispare intents em horários e/ou intervalos definidos
  • Você pode usá-los com broadcast receivers para iniciar serviços e realizar outras operações
  • Operam fora do seu app, de modo que você pode usá-los para acionar eventos ou ações mesmo quando o app não está em execução e até mesmo se o próprio dispositivo está inativo
  • Ajudam a minimizar os requisitos de recursos do app. Você pode programar operações sem depender de timers ou da execução contínua de serviços em segundo plano.

Observação: para operações de tempo com garantia de ocorrência durante o ciclo de vida do seu app, você pode usar a classe Handler com Timer e Thread. Essa abordagem oferece ao Android melhor controle sobre os recursos do sistema.

Entender os prós e contras

Um alarme recorrente é um mecanismo relativamente simples com flexibilidade limitada. Essa pode não ser a melhor opção para seu app, especialmente se você precisa acionar operações de rede. Um alarme mal projetado pode consumir bateria e sobrecarregar os servidores.

Um cenário comum para acionar uma operação fora do ciclo de vida do seu app é sincronizar dados com um servidor. Esse é um caso em que você pode ficar tentado a usar um alarme recorrente. Mas se você for o proprietário do servidor que hospeda os dados do seu app, o uso do Google Cloud Messaging (GCM) com o adaptador de sincronização é uma solução melhor do que AlarmManager. Um adaptador de sincronização oferece as mesmas opções de programação que AlarmManager, mas muito mais flexibilidade. Por exemplo, uma sincronização poderia ser baseada em uma mensagem de "novos dados" do servidor/dispositivo (consulte Como executar um adaptador de sincronização para ver mais detalhes), a atividade do usuário, a hora do dia e assim por diante. Assista aos vídeos vinculados na parte superior desta página para ver uma discussão detalhada sobre quando e como usar o GCM e o adaptador de sincronização.

Os alarmes não são acionados quando o dispositivo está inativo no modo Soneca. Todos os alarmes programados serão adiados até que o dispositivo saia do modo Soneca. Várias opções estão disponíveis se você precisa garantir que seu trabalho seja concluído mesmo quando o dispositivo estiver inativo. Você pode usar setAndAllowWhileIdle() ou setExactAndAllowWhileIdle() para garantir que os alarmes serão executados. Outra opção é usar a nova API WorkManager, que foi criada para executar trabalho em segundo plano uma única vez ou periodicamente. Para saber mais, consulte Agendar tarefas com o WorkManager.

Práticas recomendadas

Cada escolha que você faz ao criar um alarme recorrente pode ter consequências na forma como o app usa os (ou abusa dos) recursos do sistema. Por exemplo, imagine um app muito utilizado que é sincronizado com um servidor. Se a operação de sincronização for baseada no horário do relógio e todas as instâncias do app forem sincronizadas às 23h, a carga no servidor poderá resultar em alta latência ou até mesmo "negação de serviço". Siga estas práticas recomendadas para o uso de alarmes:

  • Adicione aleatoriedade (instabilidade) a todas as solicitações de rede acionadas como resultado de um alarme recorrente:
    • Faça qualquer trabalho local quando o alarme for acionado. "Trabalho local" significa qualquer coisa que não chegue a um servidor ou que exija os dados do servidor.
    • Ao mesmo tempo, programe o alarme que contém as solicitações de rede para disparar em um período aleatório.
  • Mantenha a frequência do alarme em um nível mínimo.
  • Não acione o dispositivo desnecessariamente (esse comportamento é determinado pelo tipo de alarme, conforme descrito em Escolher um tipo de alarme).
  • Não faça com que o horário de acionamento do alarme seja mais preciso do que o necessário.

    Use setInexactRepeating() em vez de setRepeating(). Quando você usa setInexactRepeating(), o Android sincroniza alarmes recorrentes de vários apps e os aciona ao mesmo tempo. Isso diminui o número total de vezes que o sistema precisa ativar o dispositivo, reduzindo, assim, o consumo da bateria. A partir do Android 4.4 (API de nível 19), todos os alarmes recorrentes são imprecisos. Observe que embora setInexactRepeating() seja uma melhoria em relação a setRepeating(), ele ainda poderá sobrecarregar um servidor se todas as instâncias de um app chegarem ao servidor ao mesmo tempo. Portanto, para solicitações de rede, adicione alguma aleatoriedade aos seus alarmes, como discutido acima.

  • Evite basear seu alarme na hora do relógio, se possível.

    Alarmes recorrentes baseados em um gatilho preciso não funcionam bem. Use ELAPSED_REALTIME, se for possível. Os diferentes tipos de alarme são descritos em detalhes na seção a seguir.

Definir um alarme recorrente

Conforme descrito acima, alarmes recorrentes são uma boa opção para programar eventos regulares ou pesquisas de dados. Um alarme recorrente tem as seguintes características:

  • Um tipo de alarme. Para saber mais, consulte Escolher um tipo de alarme.
  • Um tempo de acionamento. Se o tempo do acionador especificado estiver no passado, o alarme será acionado imediatamente.
  • O intervalo do alarme. Por exemplo: uma vez por dia, a cada hora, a cada cinco minutos e assim por diante.
  • Um intent pendente que é acionado quando o alarme é disparado. Quando você define um segundo alarme que usa o mesmo intent pendente, ele substitui o alarme original.

Para cancelar um PendingIntent, transmita FLAG_NO_CREATE para PendingIntent.getService() para receber uma instância do intent (se houver) e transmita esse intent para 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);
    }
    

Escolher um tipo de alarme

Uma das primeiras considerações sobre o uso de um alarme recorrente é o tipo dele.

Há dois tipos de relógio geral para alarmes: "tempo real decorrido" e "relógio em tempo real" (RTC, na sigla em inglês) O relógio "tempo real decorrido" usa o "tempo desde a inicialização do sistema" como referência, e o "relógio em tempo real" se adequa ao fuso horário local. Isso significa que o tempo real decorrido é adequado para definir um alarme com base na passagem do tempo (por exemplo, um alarme disparado a cada 30 segundos), porque não é afetado por fuso horário/local. O tipo "relógio em tempo real" é mais adequado para alarmes que dependem da localidade atual.

Os dois tipos têm uma versão de "ativação", que diz para ativar a CPU do dispositivo se a tela estiver desligada. Isso garante que o alarme seja disparado no horário programado. Isso será útil se seu app tiver uma dependência de tempo, por exemplo, se ele tiver uma janela limitada para executar uma operação específica. Se você não usar a versão de ativação do seu tipo de alarme, todos os alarmes recorrentes serão disparados quando o dispositivo estiver novamente em estado de alerta.

Se você simplesmente precisar que seu alarme dispare em um intervalo específico (por exemplo, a cada meia hora), use um dos tipos de "tempo real decorrido". Em geral, essa é a melhor escolha.

Se você precisar ativar o alarme em um determinado horário do dia, escolha um dos tipos de relógio em tempo real com relógio. Observe, porém, que essa abordagem pode apresentar algumas desvantagens: o app pode não converter bem para outras localidades e, se o usuário alterar a configuração de tempo do dispositivo, poderá causar um comportamento inesperado no app. O uso de um tipo de alarme de relógio em tempo real também não se adapta bem, como discutido acima. Recomendamos que você use um alarme de "tempo real decorrido", se puder.

Veja a lista de tipos:

  • ELAPSED_REALTIME: dispara o intent pendente com base no tempo decorrido desde que o dispositivo foi inicializado, mas não ativa o dispositivo. O tempo decorrido inclui qualquer momento em que o dispositivo esteve inativo.
  • ELAPSED_REALTIME_WAKEUP: ativa o dispositivo e dispara o intent pendente após o tempo especificado desde a inicialização do dispositivo.
  • RTC: dispara o intent pendente no horário especificado, mas não ativa o dispositivo.
  • RTC_WAKEUP: ativa o dispositivo para disparar o intent pendente no horário especificado.

Exemplos de alarmes de "tempo real decorrido"

Veja alguns exemplos de como usar ELAPSED_REALTIME_WAKEUP.

Ativar o dispositivo para disparar o alarme em 30 minutos e a cada 30 minutos depois disso:

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);
    

Ativar o dispositivo para disparar um alarme único (não recorrente) em um 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);
    

Exemplos de alarmes de "relógio em tempo real"

Veja alguns exemplos de como usar RTC_WAKEUP.

Ativar o dispositivo para disparar o alarme aproximadamente às 14h e repetir uma vez por dia na mesma 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);
    

Ativar o dispositivo para disparar o alarme exatamente às 8h30 e, a partir daí, a 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);
    

Decidir a precisão que o alarme precisa ter

Conforme descrito acima, escolher o tipo geralmente é o primeiro passo para criar um alarme. Outra distinção é a precisão do alarme. Para a maioria dos apps, setInexactRepeating() é a escolha certa. Quando você usa esse método, o Android sincroniza vários alarmes recorrentes imprecisos e os aciona ao mesmo tempo. Isso reduz o consumo da bateria.

Em situações raras em que os requisitos de tempo são rígidos, por exemplo, quando o alarme precisa ser disparado exatamente às 8h30 e a cada hora na hora exata daí por diante, use setRepeating(). Evite usar alarmes exatos, se possível.

Com setInexactRepeating(), não é possível especificar um intervalo personalizado como você faz com setRepeating(). Você precisa usar uma das constantes de intervalo, por exemplo, INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY e assim por diante. Consulte AlarmManager para ver a lista completa.

Cancelar um alarme

Dependendo do app, convém incluir a capacidade de cancelar o alarme. Para cancelar um alarme, chame cancel() no Gerenciador de alarmes, informando o PendingIntent que você não quer mais disparar. Por exemplo:

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);
    }
    

Iniciar um alarme quando o dispositivo é reiniciado

Por padrão, todos os alarmes são cancelados quando um dispositivo é desligado. Para evitar que isso aconteça, você pode projetar seu app para reiniciar automaticamente um alarme recorrente se o usuário reinicializar o dispositivo. Isso garante que o AlarmManager continuará fazendo sua tarefa sem que o usuário precise reiniciar manualmente o alarme.

Veja as etapas:

  1. Defina a permissão RECEIVE_BOOT_COMPLETED no manifesto do seu app. Isso permite que ele receba a ACTION_BOOT_COMPLETED que é transmitida após a inicialização do sistema. Isso só funcionará se o app já tiver sido iniciado pelo usuário pelo menos uma vez:
        <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  2. Implemente um BroadcastReceiver para receber a transmissão:

    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. Adicione o receptor ao arquivo de manifesto do seu app com um filtro de intent que filtre a ação ACTION_BOOT_COMPLETED:
    <receiver android:name=".SampleBootReceiver"
                android:enabled="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            </intent-filter>
        </receiver>

    No manifesto, o receptor de inicialização está definido como android:enabled="false". Isso significa que o receptor só será chamado se o app habilitá-lo explicitamente. Isso impede que o receptor de inicialização seja chamado desnecessariamente. Você poderá ativar um receptor, por exemplo, se o usuário definir um alarme, da seguinte maneira:

    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);
        

    Depois que você ativa o receptor dessa maneira, ele permanecerá ativado, mesmo se o usuário reinicializar o dispositivo. Em outras palavras, a ativação do receptor de forma programática substitui a configuração do manifesto, mesmo durante as reinicializações. O receptor permanecerá ativado até que o app o desative. Você pode desativar um receptor (por exemplo, se o usuário cancelar um alarme), da seguinte forma:

    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 dos recursos "Soneca" e "App em espera"

Os recursos "Soneca" e "App em espera" foram introduzidos no Android 6.0 (API de nível 23) com objetivo de aumentar a vida útil da bateria do dispositivo. Quando o dispositivo estiver no modo Soneca, todos os alarmes padrão serão adiados até que o dispositivo saia desse modo ou que uma janela de manutenção seja aberta. Se você precisar ativar um alarme mesmo no modo Soneca, use setAndAllowWhileIdle() ou setExactAndAllowWhileIdle(). O app entrará no modo App em espera quando estiver inativo, ou seja, se o usuário não o tiver usado por um período e se o app não tiver um processo em primeiro plano. No modo App em espera, os alarmes são adiados como no modo Soneca. Essa restrição é suspensa quando o app não está mais inativo ou se o dispositivo estiver conectado a uma fonte de alimentação. Para saber mais sobre como o app é afetado por esses modos, leia Otimizar para o Soneca e o App em espera.