q activities Define as w Define receptor
4.3.1 Transmitindo um Intent
Como você viu, objetos Intent permitem que você se mova de Activity em Ac tivity em um aplicativo Android, ou de um aplicativo para outro. Intents podem também transmitir eventos para qualquer receptor configurado usando um dos di- versos métodos disponíveis na classe Context, como mostrado na Tabela 4.3.
Quando você transmite Intents, você envia um evento para o segundo plano. Um Intent transmitido não invoca uma Activity, então sua tela atual geralmente permanece no primeiro plano.
Você pode também especificar uma permissão quando transmite um Intent. So- mente receptores que declararam essa permissão receberão a transmissão; todos os demais serão ignorados. Você pode usar esse mecanismo para garantir que somen- te certos aplicativos de confiança possam monitorar o que seu aplicativo faz. Você pode examinar as declarações de permissão no Capítulo 1.
Transmitir um Intent é bem simples. Você usa o objeto Context para enviá-lo e os receptores interessados o captam. O Android fornece um conjunto de trans- missões de Intent relativas à plataforma que usam essa abordagem. Em certas si- tuações, como quando o fuso horário da plataforma muda, quando o dispositivo termina de ser inicializado ou quando um pacote é adicionado ou removido, o sis- tema transmite um evento usando um Intent. A Tabela 4.4 mostra algumas das transmissões de Intent que a plataforma fornece.
Para registrar uma transmissão de Intent, você deve implementar um Broad castReceiver. Você vai fazer sua própria implementação captar o Intent BOOT_ COMPLETED fornecido pela plataforma para iniciar o serviço de alerta climático. Tabela 4.4 Ações de transmissão fornecidas pela plataforma Android
Ação Descrição
ACTION_BATTERY_
CHANGED Enviado quando o nível da bateria ou estado de carregamento muda
ACTION_BOOT_COMPLETED Enviado quando a plataforma acaba de ser inicializada
ACTION_PACKAGE_ADDED Enviada quando um pacote é adicionado à plataforma
ACTION_PACKAGE_
REMOVED Enviada quando um pacote é removido da plataforma ACTION_TIME_CHANGED Enviado quando o usuário acerta o relógio do dispositivo
ACTION_TIME_TICK Enviado a cada minuto para indicar que o relógio está funcionando
ACTION_TIMEZONE_
CHANGED Enviado quando o usuário muda o fuso horário
Tabela 4.3 Métodos para transmitir Intents
Método Descrição
sendBroadcast(Intent intent) Forma simples de transmitir um Intent. sendBroadcast(Intent intent,
String receiverPermission) Transmite um permissão que os receptores devem declarar Intent com uma String de
para receber a transmissão
sendOrderedBroadcast(Intent
intent, String receiverPermission) Transmite uma chamada de receptores um a um serialmente, parando Intent para os
quando um receptor consome a mensagem
sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int
initialCode,
String initialData, Bundle initialExtras)
Transmite um Intent e obtém uma resposta por meio do BroadcastReceiver
fornecido. Todos os receptores podem anexar dados que serão retornados no
BroadcastReceiver. Quando você usa esse método, os receptores são chamados serialmente.
sendStickyBroadcast(Intent intent) Transmite um Intent e permanece por
algum tempo depois da transmissão para que os receptores possam recuperar dados. Os aplicativos que usam esse método devem declarar a permissão BROADCAST_STICKY.
Criando um serviço de metereologia em segundo plano 117 4.3.2 Criando um receptor
Uma vez que o Service de alerta climático que você vai criar deve sempre rodar no segundo plano, é preciso iniciá-lo quando a plataforma é inicializada. Para isso, você deve criar um BroadcastReceiver que monitora a transmissão do Intent BOOT_COMPLETED.
A classe BroadcastReceiver fornece uma série de métodos que permitem a você obter e configurar um código de resultados, dados de resultado (na forma de uma String) e um Bundle extra. Isso também define um método relativo ao ciclo de vida para ser executado quando o Intent apropriado é recebido.
Você pode associar um BroadcastReceiver com um IntentFilter no código ou no arquivo de manifesto XML. Declaramos isso para o manifesto do WeatherRe- porter na Listagem 4.3, onde associamos a transmissão BOOT_COMPLETED com a classe
WeatherAlertServiceReceiver. Essa classe é mostrada na listagem a seguir.
lisTagem 4.5 A classe BroadcastReceiver WeatherAlertServiceReceiver
public class WeatherAlertServiceReceiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { context.startService(new Intent(context, WeatherAlertService.class)); } } }
Quando você cria seu próprio receptor de transmissão Intent, você estende a classe
BroadcastReceiver e implementa o método abstrato onReceive(Context c, Intent i). Em nossa implementação, iniciamos o WeatherAlertService. Essa classe Service, que vamos criar a seguir, é iniciada usando o método Context. startService(Intent i, Bundle b).
Tenha em mente que instâncias da classe receiver têm um ciclo de vida curto e focalizado. Após completar o método onReceive(Context c, Intent i,) a instância e o processo que invocaram o receptor não são mais necessários e podem ser encerrados pelo sistema. Por esse motivo, você não pode executar nenhuma operação assíncrona em um BroadcastReceiver, como iniciar um thread ou mos- trar um diálogo. Em vez disso, você pode iniciar um Service, como fizemos na Listagem 4.5, e usá-lo para realizar a tarefa.
Nosso receptor iniciou o WeatherAlertService, que irá rodar no segundo plano e avisará os usuários de condições climáticas severas no primeiro plano com um alerta baseado em Notification. Vamos examinar mais profundamente o con- ceito de Service Android.
4.4
Criando um serviço de metereologia em segundo plano
Em um aplicativo Android básico, você cria classes Activity e se move de uma tela para outra usando chamadas de Intent, como fizemos em capítulos anteriores. Essa abordagem funciona para o aplicativo Android canônico de primeiro plano, que vai de tela em tela, mas não funciona para casos como o nosso, onde queremos
sempre monitorar as mudanças no clima, mesmo que o usuário não tenha nosso aplicativo aberto no momento. Para isso, precisamos de um Service.
Nesta seção, vamos implementar o
WeatherAlertService que lançamos na Lista- gem 4.4. Esse Service envia um alerta ao usuário quando descobre que há condições climáticas se- veras em uma localidade especificada. Esse alerta será mostrado sobre qualquer aplicativo, na forma de uma Notification. A Figura 4.5 mostra a notifi- cação que iremos enviar.
Uma tarefa de segundo plano é tipicamente um processo que não envolve interação direta do usuá- rio ou qualquer tipo de IU. Esse processo descreve perfeitamente a verificação de clima severo. Depois que um Service é iniciado, ele roda até que seja parado explicitamente ou até que o sistema o in- terrompa. A tarefa de segundo plano Weather Alert Service, que é iniciada quando o dispositivo é carregado por meio do Broadcast Receiver da Listagem 4.5, é mostrada na listagem a seguir.
lisTagem 4.6 Classe WeatherAlertService, para registrar localidades e enviar alertas
public class WeatherAlertService extends Service { private static final String LOC = "LOC"; private static final String ZIP = "ZIP";
private static final long ALERT_QUIET_PERIOD = 10000; private static final long ALERT_POLL_INTERVAL = 15000; public static String deviceLocationZIP = "94102"; private Timer timer;
private DBHelper dbHelper; private NotificationManager nm;
private TimerTask task = new TimerTask() { public void run() {
List<Location> locations = dbHelper.getAllAlertEnabled(); for (Location loc : locations) {
WeatherRecord record = loadRecord(loc.zip); if (record.isSevere()) { if ((loc.lastalert + WeatherAlertService.ALERT_QUIET_PERIOD) < System.currentTimeMillis()) { loc.lastalert = System.currentTimeMillis(); dbHelper.update(loc); sendNotification(loc.zip, record); } } }
. . . alerta de localização de dispositivo omitido por brevidade
} };
private Handler handler = new Handler() { public void handleMessage(Message msg) {
q
Obtém localidades com alertas habilitadosw
Dispara alerta se clima for severo Figura 4.5 Alerta de um aplicati-vo rodando em segundo plano so- bre clima severo.
Criando um serviço de metereologia em segundo plano 119
notifyFromHandler((String) msg.getData()
.get(WeatherAlertService.LOC), (String) msg.getData() .get(WeatherAlertService.ZIP));
} };
@Override
public void onCreate() {
dbHelper = new DBHelper(this); timer = new Timer();
timer.schedule(task, 5000,
WeatherAlertService.ALERT_POLL_INTERVAL); nm = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE); }
. . . onStart com LocationManager e LocationListener \ omitidos por brevidade
@Override
public void onDestroy() { super.onDestroy(); dbHelper.cleanup(); }
@Override
public IBinder onBind(Intent intent) { return null;
}
protected WeatherRecord loadRecord(String zip) { final YWeatherFetcher ywh =
new YWeatherFetcher(zip, true); return ywh.getWeather();
}
private void sendNotification(String zip, WeatherRecord record) {
Message message = Message.obtain(); Bundle bundle = new Bundle();
bundle.putString(WeatherAlertService.ZIP, zip); bundle.putString(WeatherAlertService.LOC, record.getCity() + ", " + record.getRegion()); message.setData(bundle); handler.sendMessage(message); } private void
notifyFromHandler(String location, String zip) {
Uri uri = Uri.parse("weather://com.msi.manning/loc?zip=" + zip); Intent intent = new Intent(Intent.ACTION_VIEW, uri);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, Intent.FLAG_ACTIVITY_NEW_TASK, intent,PendingIntent.FLAG_ONE_SHOT);
final Notification n =
new Notification(R.drawable.severe_weather_24, "Severe Weather Alert!",
System.currentTimeMillis());
n.setLatestEventInfo(this, "Severe Weather Alert!", location, pendingIntent); nm.notify(Integer.parseInt(zip), n); } }
e
Notifica a IU do handlerr
Inicializa o timert
Limpa a conexão com o banco de dados Exibe notificação acionávely
WeatherAlertService estende o Service. Criamos um Service de maneira se- melhante a como criamos activities e receptores de broadcast: estendemos a classe básica, implementamos os métodos abstratos e redefinimos os métodos de ciclo de vida conforme necessário.
Depois da declaração inicial de classe, definimos diversas variáveis membros. Em primeiro lugar, as constantes que descrevem nossos intervalos de pesquisa de clima severo e um período de silêncio. Nós configuramos um limite baixo para a pesqui- sa durante o desenvolvimento — alertas de clima severo vão encher o emulador devido a esse ajuste. Na produção, vamos limitar isso a uma verificação de poucas em poucas horas.
A seguir, nossa variável TimerTask vai nos permitir pesquisar o clima periodica- mente. A cada vez que a tarefa é executada, ela obtém todas as localidades salvas pelo usuário por meio de uma chamada ao banco de dados
q
. Vejamos os detalhes do uso de um banco de dados Android no Capítulo 5.Quando temos as localidades salvas, examinamos cada uma e carregamos o rela- tório climático. Se o relatório mostrar uma previsão de clima severo, atualizamos o horário do campo do último alerta e chamamos um método auxiliar para iniciar o envio de uma Notification
w
. Depois que processamos as localidades salvas pelo usuário, obtemos a localização do dispositivo usando um código postal. Se o usuário houver requisitado alertas para sua localização atual, repetimos o processo de pesqui- sa e enviamos também um alerta para a localização atual do dispositivo. Você pode ver mais detalhes sobre os recursos de localização do Android no Capítulo 11.Depois de definir nosso TimerTask, criamos uma variável Handler. Essa variável irá receber um objeto Message disparado de um thread não pertencente à IU. Nesse caso, depois de receber a Message, nosso Handler chama um método auxiliar que cria uma instância e exibe uma Notification
e
.A seguir, redefinimos os métodos Service de ciclo de vida, começando com
onCreate(). Esse é o núcleo do nosso Service: um Timer
r
que configuramos para execução repetida. Enquanto o Service continua a rodar, o timer irá nos permitir atualizar as informações do relatório climático. Depois de onCreate(), você observa onDestroy(), onde limpamos nossa conexão ao banco de dadost
. As classes Service fornecem esses métodos de ciclo de vida para que você possa controlar como os recursos são alocados e deslocados, de modo similar às classesActivity.
Depois dos métodos de ciclo de vida, implementamos o método onBind() neces- sário. Esse método retorna um IBinder, que outros componentes que chamam mé- todos Service usam para comunicação. WeatherAlertService realiza somente uma tarefa de segundo plano, ele não suporta vinculações, então, retorna null para onBind. Vamos adicionar vinculação e comunicação interprocessos (IPC) na seção 4.5.
A seguir, vamos implementar nossos métodos auxiliares. Primeiro, loadRecord()
chama a API Yahoo! Weather por meio do YWeatherFetcher. (Vamos cobrir tarefas de rede, similares àquelas que essa classe executa, no Capítulo 6). Então, sendNo tification configura uma Message com detalhes de localização para ativar o Han dler que declaramos antes. Por último, temos o método notifyFromHandler(). Esse método dispara uma Notification com objetos Intent que irão chamar a
Comunicando-se com o WeatherAlertService a partir de outros aplicativos 121 Agora que discutimos o objetivo de Services e que você criou uma classe Ser vice e iniciou uma por meio de um BroadcastReceiver, podemos começar a examinar como outros desenvolvedores interagem com seu Service.
Um aviso sobre serviços de longa duração
Nosso aplicativo de exemplo inicia um Service e o deixa rodando em segundo plano. Esse Service é projetado para ter um consumo mínimo de memória, mas as melhores práticas do Android desencorajam Services de longa duração. Services que rodam contínua e constantemente usam a rede ou realizam tarefas pesadas para a CPU vão consumir a bateria do dispositivo e podem deixar outras operações mais lentas. Pior ainda, já que rodam em segundo plano, o usuário não vai saber que aplicativos são responsáveis pelo desempenho pobre dos seus dispositivos.
O sistema operacional irá, por fim, interromper Services em execução se precisar de mais memória, mas não vai interferir de outro modo com serviços mal projetados. Se seu caso de uso não precisar mais do Service, você deve interrompê-lo. Se precisar de um Service de longa duração, você pode dar ao usuário a opção de usá-lo ou não.
4.5
Comunicando-se com o WeatherAlertService a partir de outros
aplicativos
No Android, cada aplicativo é executado dentro dos seus próprios processos. Outros aplicativos não podem chamar métodos diretamente no nosso serviço de alerta do clima, porque os aplicativos estão em caixas de areia separadas. Você já viu como um aplicativo pode invocar outro usando um Intent. Suponha que você quer saber algo específico de um determinado aplicativo, como verificar o clima em uma região específica. Esse tipo de informação granular não está prontamente disponível por meio de simples comunicação por Intent, mas felizmente o Android fornece uma solução nova: IPC por meio de um bound service.
Vamos ilustrar os bound services expandindo o alerta do clima com uma interfa- ce remota usando AIDL e, então, vamos nos conectar àquela interface por meio de um Proxy que vamos expor usando um novo Service. Ao longo do caminho, vamos explorar as classes IBinder e Binder que o Android usa para enviar mensagens e tipos durante o IPC.