quarta-feira, 4 de dezembro de 2013

Alarmes e Broadcast Receivers

Olá à todos.

Em Android, Alarmes e Broadcast Receivers caminham junto. Por isso, veremos ambos os tópicos de forma conjunta.

A classe AlarmManager fornece acesso aos serviços de alarme do sistema. Estes permitem que você programe seu aplicativo para ser executado em algum momento no futuro. Quando o alarme dispara, a Intent que havia sido registrada por este é transmitido pelo sistema, iniciando automaticamente o aplicativo de destino se ele não estiver em execução. Alarmes registrados são mantidos no sistema enquanto o dispositivo está dormindo (e pode, opcionalmente, acordar o dispositivo durante esse tempo), mas serão apagados se ele for desligado e reiniciado.

O AlarmManager mantém o alarme na CPU enquanto o método onReceive() (de um Broadcast Receiver) estiver em execução. Isso garante que o telefone não durma até que seja finalizada a transmissão do alarme. Uma vez que o onReceive() retorne, o AlarmManager liberará o alarme da CPU. Isso significa que seu telefone irá dormir em alguns casos (assim que o seu método onReceive() completar).

Já um Broadcast Receiver simplesmente responde à mensagens transmitidas por outros aplicativos ou do próprio sistema. Estas mensagens são por vezes chamadas eventos ou Intent’s. Um Broadcast Receiver é um componente em Android que permite interagirmos com os eventos do sistema ou da aplicação. Todos os Receivers inscritos para um evento serão notificados pelo Android uma vez este evento aconteça.

Exemplo de Alarme com Broadcast Receiver


Em nosso primeiro exemplo, criaremos um exemplo simples de alarme em que uma Activity interage com um Broadcast Receiver.

Nele, primeiramente, chamaremos um alarme 5 segundos depois da Activity ser chamada. E, por fim, o faremos repetir a cada 10 segundos.

Antes de mais nada, precisamos definir nosso Broadcast Receiver no AndroidManifest.xml.


Podemos notar acima que o Broadcast Receiver foi definido através de um IntentFilter. Esta é outra maneira de se declarar um componente (Activity, Service, Broadcast Receiver) no AndroidManifest.xml. Como isto é chamado, veremos ao longo deste exemplo.

A Activity que chama nosso alarme é mostrada abaixo.


No método onCreate() é chamado o método schedule(). Neste método, inicialmente, colocamos o IntentFilter “EXECUTE_ALARM” dentro de uma PendingIntent. Depois, inicializamos a hora em que o alarme será chamado, através do Calendar. E então, chamamos o alarme, utilizando tanto a PendingIntent como o Calendar. No caso do nosso exemplo, o alarme será chamado 5 segundos depois que a Activity for inicializada.

Após estes 5 segundos, a PendingIntent será chamada. Ou seja, o fluxo do nosso aplicativo irá para a classe AlarmReceiver (que é o Broadcast Receiver associado com o IntentFilter “EXECUTE_ALARM”).

A classe AlarmReceiver é bem simples.


Ou seja, nesta classe tudo que fazemos é chamar um Toast.

E pronto! Acabamos de criar nosso primeiro exemplo de alarme em Android.

Agora, iremos fazer este alarme ser repetido a cada 10 segundos. Para isto, criaremos uma classe chamada RepeatedAlarmActivity.


A principal diferença entre a RepeatedAlarmActivity e a SimpleAlarmActivity é o comando abaixo:
alarm.setRepeating(AlarmManager.RTC_WAKEUP, time, REPEATING_TIME, p), que realiza a repetição do alarme, à cada 10 segundos.

De resto, a RepeatedAlarmActivity é praticamente idêntica à SimpleAlarmActivity.

Agora, veremos um exemplo mais complexo de alarme, em que utilizaremos, além das classes AlarmManager e BroadcastReceiver, as classes Notification e Service.

Exemplo de alarme com Service e Broadcast Receiver usando RECEIVE_BOOT_COMPLETED


Neste exemplo, programaremos o alarme para ser executado em uma hora específica. E para isto, precisamos assegurar que o alarme será executado mesmo que o celular seja reiniciado.

Para isto, precisamos modificar nosso Broadcast Receiver para executar quando o celular for ligado. Isto pode ser feito através do IntentFilter BOOT_COMPLETED. Além deste IntentFilter, precisamos declarar uma permissão específica. Ambos são mostrados abaixo.


Como podemos notar pelo AndroidManifest.xml, temos neste exemplo duas Activity, um Service e um Broadcast Receiver, e este funciona tanto quando nosso aplicativo estiver sendo executado, quando o celular for reiniciado.

Porém, se ele for executado quando o BOOT do sistema for realizado, não poderemos utilizar logs  de Logcat, e tampouco debugar a aplicação, uma vez que ao ser reinicializado, o sistema fecha todas as aplicações que estavam abertas antes de seu desligamento.

Por este motivo, neste exemplo, utilizaremos o conceito ensinado no Capítulo 11 para criar “logs” em arquivos de texto dentro de nosso dispositivo Android.

Logo, neste aplicativo, utilizaremos a classe FileUtils (tal qual demonstrado no Capítulo 11) para salvar dados de log em um arquivo localizado em nosso dispositivo Android. Esta classe será chamada diretamente pela classe LogUtils, que é mostrada abaixo.


Ou seja, nesta classe, tudo que faremos será imprimir uma mensagem em um arquivo.

Agora que temos assegurado nosso log, podemos executar a lógica que chamará o alarme.

Como pudemos ver no AndroidManifest.xml, neste exemplo temos duas Activity, um Service e um Broadcast Receiver. A lógica que usaremos será análoga ao primeiro exemplo de alarme, ensinado na seção anterior deste Blog. Ou seja, na tela principal chamaremos um Broadcast Receiver.

No exemplo anterior, o chamávamos através de um IntentFilter chamado EXECUTE_ALARM, colocado em uma Intent que era anexada à um PendingIntent. Neste exemplo, o chamaremos através do próprio nome do Broadcast Receiver, que é PushNotificationReceiver.

Abaixo podemos ver como este Receiver é chamado na tela principal.


Após ser chamado, este Receiver executará primeiramente o método onReceive(). Como podemos ver abaixo, ele recebe um contexto (pois Receivers nativamente não possuem contexto), e chamar o método schedule().


É no método schedule() que chamamos o Service, colocando-o em uma chamada de Intent, e colocando esta em uma PendingIntent. Neste método, caso a PendingIntent não tenha sido criada (ou seja, o alarme não tenha sido programado ainda), chamamos o método setAlarm(). Caso ela tenha sido, imprimimos em nosso log uma mensagem de alerta.


Um dos argumentos do método setAlarm() é um retorno do método getAlarmTime(). Neste método, obtemos o horário ao qual programamos nosso alarme (que é definido no arquivo string.xml). E nele também realizamos a comparação mais importante de todo o Receiver: a atualização da variável currTime.

O if deste Receiver atualiza o valor desta variável usando a seguinte lógica: se o alarme não tocou no dia atual, não entramos neste if. Porém, se o alarme já tocou (ou seja, se a hora e minuto correntes forem maiores que a hora e o minuto do alarme), atualizamos o currTime para o dia de amanhã, pelo comando currTime += 24 * 60 * 60 * 1000. Estamos somando o tempo atual à 1000 milisegundos, multiplicados por 60 segundos, multiplicados por 60 minutos, multiplicados por 24 horas (ou seja, estamos somando currTime ao dia de amanhã, 24 horas posteriores ao momento atual).


Em seguida, setamos currTime na variável calendar, e retornamos este valor em milissegundos.

Em sequência, temos a definição do método setAlarm().


Neste método, chamamos o AlarmManager através dos já inicializados pendingIntent e time (que nada mais é do que o retorno do recém explicado getAlarmTime()).

E assim chegamos ao fim do nosso PushNotificationReceiver, que chama o PushNotificationService. Este Service será chamado apenas quando chegarmos EXATAMENTE ao horário programado pelo AlarmManager. E como definimos um Receiver em BOOT, mesmo que o celular seja reiniciado, o Service será chamado.

Então,vamos à implementação do Service:


Podemos notar acima que temos três constantes:

  • NOTIFICATION_INTENT É uma String que define um IntentFilter (tal qual esta definido em nosso AndroidManifest.xml). Neste caso, define a tela NotificationActivity.java.
  • FROM_NOTIFICATION_EXTRA_KEY Identificador de um Extra de Intent que enviaremos à tela NotificationActivity, com o intuito de cancelar a notificação chamada pelo Service.
  • NOTIFICATION_ID Identificador da nossa notificação. É o dado que será enviado juntamente com a FROM_NOTIFICATION_EXTRA_KEY para a tela NotificationActivity.


Temos também um atributo que armanena uma instância de FileUtils, ao qual chamaremos a classe LogUtils.

Repare também que temos o evento onBind(). Este evento não tem muita importância na implementação do nosso Service.


Acima podemos ver o método onCreate(), onde chamamos o método createNotification() (onde criamos a notificação); cancelamos as Intent anteriores; retransmitimos o alarme para o dia de amanhã; e, por fim, finaliza a si mesmo.

Logo abaixo, temos o método createNotification(), que nada mais é do que uma revisão do que foi explicado no Blog, neste tópico. Nele, criamos uma notificação que chama a Intent NotificationActivity.


E abaixo, temos o método que cancela alarmes anteriores. Este método é chamado entre o método createNotification() e o comando sendBroadcast(new Intent(this, PushNotificationReceiver.class)). Ou seja, neste exato momento (entre o método o comando descritos acima), precisamos certificar que nenhum alarme está programado para ser tocado (uma vez que precisamos programar um novo alarme). Após nos certificarmos disso, aí sim podemos chamar o próximo alarme pelo método sendBroadcast().


Repare acima que a PendingIntent chamou o IntentFilter NOTIFICATION_INTENT. Verificando nosso AndroidManifest.xml, podemos notar que a classe associada à este IntentFilter é a PushNotificationService. Ou seja, estamos obtendo as PendingIntent associadas ao nosso Service.

Por fim, vamos à implementação da tela NotificationActivity, que é a tela chamada após o alarme ser tocado.


Acima podemos notar que chamamos o método cancelNotificationIfNeeded(). Basicamente é isto que faremos nesta tela. Uma vez que a classe PushNotificationService é executada, e a notificação é mostrada, chamamos a tela atual via PendingIntent, e nela precisamos cancelar a notificação. Caso contrário, ela será mostrada eternamente.


Acima podemos notar de que maneira a notificação é cancelada: recebemos um Extra de Intent do nosso Service (que nada mais é do que o id da nossa notificação), e com ele, a cancelamos.

E isto é tudo! Já temos conhecimento para criar um alarme executável em um horário específico, mesmo que para tal precisemos reiniciar nosso celular.

-----

Caso alguém tenha uma dúvida, crítica ou sugestão, sinta-se à vontade.

Nenhum comentário:

Postar um comentário