sexta-feira, 18 de setembro de 2015

BroadcastReceiver

Nota: Peço desculpas pelo atraso das postagens devido a assuntos pessoais, informo também que as postagens podem atrasar um pouco devido a algumas mudanças e melhorias relacionadas às postagens (e a manutenção que realizei no meu pc a algumas semanas), prezando pela qualidade ao invés da quantidade, então sem mais delongas e vamos aos estudos.

Hoje falaremos sobre a classe BroadcastReceiver uma das classes mais importantes na arquitetura do Android, utilizada para que aplicações possam reagir a determinados eventos gerados por uma intent (mais informações sobre inten aqui:).

A classe BroadcastReceiver é sempre executada em segundo plano durante um curto espaço de tempo sem utilizar a interface gráfica, com o objetivo de receber uma mensagem e processá-la sem o usuário perceber. Um passo importante na integração de aplicações uma vez que elas podem trocar mensagens em segundo plano sem atrapalhar o usuário.

Introdução

A classe android.content.BroadcastReceive é utilizada para responder a determinados eventos iniciados por uma intente, por exemplo, executar uma determinada tarefa em segundo plano ou determinada aplicação ao receber uma mensagem SMS, uma ligação telefônica ou qualquer outra ação mapeada da aplicação.

Para configurar o BroadcastReceiver, basta configurar a tag <receiver> no arquivo AndroidManifest.xml combinando-a com a tag <intente-filter> para definir uma ação e categoria.
Podemos ver a seguir a configuração da classe Receiver1 que pode interceptar uma mensagem que tenha a ação RECEIVER1:

Configurações Receiver1

Note que a configuração realizada no arquivo AndroidManifest.xml é a mesma para uma <activity> e <receiver>, onde ambas declaram o nome de uma classe e um <intente-filter> com a ação e categoria para a qual a classe deve executar.

O código-fonte da classe Receiver1 pode ser visto a seguir. Note que apenas o método onReceiver(contexto, intent) precisa ser implementado. Assim quando a mensagem com a ação RECEIVER1 for disparada a classe Receiver1 será executada em segundo plano sem interferir na tela do usuário.

Configurando um BroadcastReceiver

Um BroadcastReceiver pode ser configurado de duas formas: 
  1. Confiurando o arquivo AndroidManifest.xml com a tag <receiver>. A tag <intente-filter> é usada pra configurar qual ação e categoria devem ser executados;
  2. Utilizando o método contente.registerReceiver(receiver, filtro) dentro do código para registrar dinamicamente o BroadcastReceiver. O primeiro parâmetro é uma instância de uma subclasse(classe-filha) de IntentReceiver, o segundo é uma instância da classe IntentFilter, que possui a configuração de ação e categoria; 

Projeto de exemplo

Não há muito mais o que falar de um BroadcastReveiver então partiremos para a prática. No final do post você encontra o link para download do projeto completo AppBroadcastReceiver no Eclipse.
O projeto possui um menu com opções de forma a organizar todos os exemplos em um só aplicativo conforme a figura abaixo:


Menu da aplicação

Arquivo AndroidManifest.xml completo:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="br.app.blog.appbroadcastreceiver"

    android:versionCode="1"

    android:versionName="1.0" >



    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="23" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name="br.app.blog.appbroadcastreveicer.receiv.Receiver1">
            <intent-filter>
                <action android:name="RECEIVER1"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>
        <receiver android:name="br.app.blog.appbroadcastreveicer.receiv.Receiver2">
            <intent-filter>
                <action android:name="RECEIVER2"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>
        <receiver android:name="br.app.blog.appbroadcastreveicer.receiv.Receiver3">
            <intent-filter>
                <action android:name="RECEIVER_ANR"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>
        <receiver android:name="br.app.blog.appbroadcastreveicer.receiv.Receiver4"></receiver>
        <activity android:name=".ActivityTeste"></activity>
        <receiver android:name="br.app.blog.appbroadcastreveicer.receiv.ReceiverBoot">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>


Note que várias classes foram declaradas no arquivo AndroidManifes.xml com a tag <receiver> exceto a classe Receiver2 que será registrada manualmente pela api no código da classe MainActivit.class (Menu). 

Nota:
Há outras configurações no AndroidManifest.xlm como a permissão RECEIVE_BOOT_COMPLETED, que serão explicadas mais a frente.

A seguir podemos ver a classe MainActivity que define uma lista com as opões de exemplo:

package br.app.blog.appbroadcastreceiver;



import android.annotation.SuppressLint;

import android.content.Intent;

import android.content.IntentFilter;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import br.app.blog.appbroadcastreveicer.receiv.Receiver2;
import br.app.blog.appbroadcastreveicer.receiv.Receiver4;
import android.widget.ArrayAdapter;
import android.widget.ListView;

@SuppressLint("NewApi")
public class MainActivity extends AppCompatActivity implements OnItemClickListener {

         private static final String CATEGORIA = "livro";
         private ListView lista;
         private String[] opcoes = { "Receiver 1", "Receiver2 - API", "Receiver3 - ANR",
      "Receiver4 - Iniciar Activity", "Receiver5 - Integrar outra aplicação -     APP_TESTE", "Sair" };
        
         @Override
         protected void onCreate(Bundle savedInstanceState) {
                  
                   super.onCreate(savedInstanceState);
                   setContentView(R.layout.activity_main);
                   inicializarComponentes();
                   registerReceiver(new Receiver2(), new IntentFilter("RECEIVER2"));
        
         }

         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position,         long  id) {
                  
                   switch (position) {
                  
                   case 0: sendBroadcast(new Intent("RECEIVER1")); break;
                   case 1: sendBroadcast(new Intent("RECEIVER2")); break;
                   case 2: sendBroadcast(new Intent("RECEIVER_ANR")); break;
                   case 3: sendBroadcast(new Intent(this, Receiver4.class)); break;
                   case 4: sendBroadcast(new Intent("APP_TESTE")); break;
                   default: finish();
                  
                   }
                  
         }
        
         private void inicializarComponentes() {
                  
                   lista = (ListView) findViewById(R.id.lista);
                   lista.setOnItemClickListener(this);
                   lista.setAdapter(new ArrayAdapter<String>(this,   android.R.layout.simple_list_item_1, opcoes));
                  
         }
        
         @Override
         protected void onDestroy() {
                  
                   super.onDestroy();
                   Log.i(CATEGORIA, "onDestroy");
                   unregisterReceiver(new Receiver2());
                  
         }

} 

Note que além de definirmos um menu com as opções do exemplo, o método registerReceiver(receiver, filter) é chamado para registrar dinamicamente a classe Receiver2 com a ação RECEIVER2.
Como registramos um Broadcast pela API Java, é necessário chamar o método unregisterReceiver(receiver) informando uma instância da mesma classe utilizada para registrar. Essse método pode ser chamado no onDestroy(). 

Nota:

Registrar o receiver dinamicamente deve ser usado quando é necessário que ele seja utilizado somente quando determinada tela da aplicação estiver executando, caso contrário, não. Se for registrado de forma estática no arquivo AndroidManifest.xml ele pode executar a qualquer momento que receber uma mensagem (intent), mesmo quando a aplicação não estiver executando.


Agora mostraremos o código-fonte das classes Receiver1 e Receiver2 que apenas exibem uma mensagem na tela:

Receiver1:



Receiver2 :

Note que exibir um alerta na tela não é o que o BroadcastReceiver deve fazer, pois ele deve ser utilizado justamente para o contrário: executar em segundo plano e nunca atrapalhar o usuário, porem isso é apenas um exemplo e o alerta permite que se veja claramente quando a classe está sendo executada. 

Ciclo de Vida


Um BroadcastReceiver é válido somente durante a chamada do método onReceive(context, intent). Depois disso o sistema operacional encerrará seu processo para liberar memória. O método onReceive(context, intent) deve executar em um curto espaço de tempo e assim que terminar o Android vai considerar que o BroadcastReceiver não está mais ativo e o destruirá.

Este método deve consumir rapidamente a mensagem (intent) recebida e retornar. Caso demore mais de 10 segundos para executar, o Android exibirá um erro ANR (Application Not Responding), que nada mais é do que um timeout:


 

Erro ANR
Nota:
Um Broadcast deve executar em, no máximo, 10 segundos para evitar o erro ANR. Outra forma de acontecer o erro ANR é quando a activity atual não consegue responder a um evento de entrada (tecla pressionada) em 5 segundos.

O problema desses 10 segundos é que são fáceis de serem extrapolados. Imagine que você precise executar um sincronismo pela internet (download, por exemplo) em segundo plano. Dependendo da conexão e da quantidade de dados a serem trafegados, isso pode demorar um pouco.
Lembre-se também que para um processo demorado com acesso a internet, ou não, é necessário uma Thread, porém não devemos nos esquecer dos 10 segundos de limite e o mais importante, ‘Um BroadcastReceiver só é válido somente durante a chamada do método onReceive(context, intent), após sua chamada o sistema operacional encerrará seu processo para liberar memória’.
A Thread é assíncrona e o Broadcast quando for executado iniciará e encerrará sua execução independente da Thread ter concluído a execução ou não, essa Thread não faz parte de nenhum ciclo de vida que o android conheça e como resultado a sua Thread pode ser encerrada e o sincronismo com a internet interrompido.
Para resolver o problema foi criada a classe androd.app.Service (que será estudada em outro post) que pode executar processos assíncronos em segundo plano por tempo indeterminado. 

Nota: 
É recomendado que, se um BroadcastReceiver precisar executar uma tarefa longa, a classe android.app.Service seja utilizada. Caso um BroadcastReceiver abra uma Thread o Android pode encerrar aquele processo e informações importantes podem ser perdidas.

Executar um BroadcastReceiver ao inicializar o SO

O Projeto AppBroadcastReceiver (link no final do post) já tem configurada a classe ReceiverBoot, que é executada assim que o sistema operacional terminar a inicialização (boot).
Isso é possível porque, quando o sistema operacional termina a inicialização dispara um sendBroadcast(intent) com a ação android.intent.action.BOOT_COMPLETED para informar a todas as aplicações que o boot terminou.
A mensagem é enviada a todas as aplicações instaladas. Nesse casso é possível interceptar a mensagem e iniciar um BroadcastReceiver automaticamente logo após o sistema operacional terminar a inicialização.
A seguir podemos analisar a configuração da classe ReceiverBoot no arquivo AndroidManifest.xml:

É necessário declarar a permissão RECEIVE_BOOT_COMPLETED para utilizar a ação BOOT_COMPLETED: 

A seguir podemos ver a classe ReceiverBoot que apenas imprime uma mensagem no logcat: 

Para testar o exemplo feche o emulador, caso esteja aberto, e execute-o novamente, a mensagem aparecerá no logcat após a inicialização do sistema operacional: 

 


Configurando uma aplicação para ser iniciada apenas por um BroadcastReceiver


Nos exemplos mostrados anteriormente foi criada uma activity “top_level” para ser iniciada pelo usuário. Isso não é obrigatório para o Android e é possível construir aplicações que não precisem definir uma interface gráfica.
Para isso configuramos um BroadcastReceiver que pode ser executado ao receber determinada mensagem (intent). Este pode iniciar um serviço em segundo plano para realizar alguma tarefa longa e avisar o usuário de algum acontecimento utilizando uma notificação.
Para mostrar como iniciar uma aplicação a partir de um BroadcastReceiver e crie um projeto chamado AppBroadcastAbrirAplicacao ou baixe o projeto completo no link ao final do post.
Ao criar um novo projeto ele deve ser criado em um pacote diferente de algum já registrado caso contrário não será possível instalar a aplicação.
Para iniciar a aplicação será usado um BroadcastReceiver, configurado para executar com a ação APP_TESTE, de acordo com o arquivo AndroidManifest.xml mostrado a seguir:

A seguir podemos ver a classe ReceiverAbrirApp, que ao executar mostra uma simples mensagem no Logcat (ou Toast):

Agora que criamos a nova aplicação precisamos instalar no emulador, para isso clique com o botão direito em cima do projeto e selecione a opção RunAs>Android Application.
A aplicação criada não tem nenhuma activity, de modo que, ao executar o projeto nenhuma tela aparece. Mas podemos visualizar na janela console do Eclipse que a aplicação foi instalada com sucesso.

Para testar a aplicação é só criar uma intent com a ação APP_TESTE. O Android vai verificar qual BroadcastReceiver está configurado para a ação informada, e nesse caso, executará a classe ReceiverAbrirApp desse exemplo.

Abertura de uma tela apartir de um BroadcastReceiver

Um BroadcastReceiver é executado em segundo plano se atrapalhar o usuário, esse é seu sentido. Mas caso seja necessário abrir uma tela da aplicação a partir de um BroadcastReceiver, embora não recomendado, pode ser realizado.
Para isso é possível utilizar o método startActivity(intent) normalmente, porém é necessário configurar a flat Intent.NEW_TASK_LAUNCH.
Um exemplo pode ser visto na activity Receiver4 do projeto AppBroadcastReceiver:

Chamar uma activity a partir de um BroadcastReceiver não é recomendado pelo Android, porque isso pode interromper a tarefa que o usuário está fazendo.

Interação com o usuário por meio de notificações 

Para finalizar devemos lembrar que um BroadcastReceiver não deve interagir com o usuário de forma direta, por exemplo, mostrando um alerta com a classe Toast ou abrindo uma tela sem perguntar ao usuário se ele deseja ou não. O usuário pode estar fazendo algo importante e provavelmente não desejaria ser interrompido. A maneira mais indicada de uma aplicação que está executado em segundo plano interagir com o usuário é através de uma notificação. Uma notificação é um tipo de alerta que fica na barra de status do Android e chama a atenção do usuário, mas esse assunto fica pra próxima!



Nenhum comentário:

Postar um comentário