Visão geral de transmissões

Os apps para Android podem enviar ou receber mensagens de transmissão do sistema Android e de outros apps Android, de modo semelhante ao padrão de design publicação-inscrição (Pub/Sub). Essas transmissões são enviadas quando ocorre um evento de interesse. Por exemplo, o sistema Android envia transmissões quando ocorrem vários eventos, como quando o sistema é iniciado ou o dispositivo começa a carregar. Os apps também podem enviar transmissões personalizadas, por exemplo, para notificar outros apps sobre algo que possa interessar a eles, como o download de alguns dados novos.

Os apps podem se registrar para receber transmissões específicas. Quando uma transmissão é enviada, o sistema a direciona automaticamente para apps que se inscreveram para receber esse tipo específico de transmissão.

No geral, as transmissões podem ser usadas como um sistema de troca de mensagens entre apps e fora do fluxo normal do usuário. No entanto, você precisa ter cuidado para não abusar da oportunidade de responder a transmissões e executar jobs em segundo plano que possam contribuir para um desempenho lento do sistema, conforme descrito no vídeo a seguir.

Sobre transmissões do sistema

O sistema envia transmissões automaticamente quando ocorrem vários eventos, como quando ele entra e sai do modo avião. As transmissões do sistema são enviadas para todos os apps inscritos para receber o evento.

A mensagem de transmissão em si é agrupada em um objeto Intent, cuja string de ação identifica o evento que ocorreu, por exemplo, android.intent.action.AIRPLANE_MODE. O intent também pode incluir outras informações empacotadas no campo extra. Por exemplo, o intent do modo avião inclui um booleano extra que indica se o modo está ativado ou não.

Para mais informações sobre como ler intents e conseguir a string de ação de um intent, consulte Intents e filtros de intents.

Para ver uma lista completa das ações de transmissão do sistema, consulte o arquivo BROADCAST_ACTIONS.TXT no SDK Android. Cada ação de transmissão tem um campo de constante associado a ela. Por exemplo, o valor da constante ACTION_AIRPLANE_MODE_CHANGED é android.intent.action.AIRPLANE_MODE. A documentação de cada ação de transmissão está disponível no campo de constante associado.

Mudanças em transmissões do sistema

À medida que a plataforma Android evolui, ela muda periodicamente a forma como as transmissões do sistema se comportam. Tenha as seguintes mudanças em mente se o app for voltado ao Android 7.0 (API de nível 24) ou versões posteriores ou se ele for instalado em dispositivos com o Android 7.0 ou versões posteriores.

Android 9

A partir do Android 9 (API de nível 28), a transmissão NETWORK_STATE_CHANGED_ACTION não recebe informações sobre o local do usuário nem dados de identificação pessoal.

Além disso, se o app estiver instalado em um dispositivo com o Android 9 ou versão posterior, transmissões do sistema por Wi-Fi não terão SSIDs, BSSIDs, informações de conexão nem resultados de verificação. Para receber essas informações, chame getConnectionInfo().

Android 8.0

A partir do Android 8.0 (API de nível 26), o sistema impõe outras restrições aos receptores declarados pelo manifesto.

Se seu app for voltado ao Android 8.0 ou versões posteriores, não será possível usar o manifesto para declarar um receptor para a maioria das transmissões implícitas, isto é, transmissões que não segmentam seu app de modo específico. Você ainda pode usar um receptor registrado pelo contexto quando o usuário estiver usando seu app ativamente.

Android 7.0

O Android 7.0 (API de nível 24) e versões posteriores não envia as seguintes transmissões do sistema:

Além disso, os apps voltados ao Android 7.0 e versões posteriores precisam registrar a transmissão CONNECTIVITY_ACTION usando registerReceiver(BroadcastReceiver, IntentFilter). A declaração de um receptor no manifesto não funcionará.

Como receber transmissões

Os apps podem receber transmissões de duas maneiras: por meio de receptores declarados pelo manifesto e por receptores registrados pelo contexto.

Receptores declarados pelo manifesto

Se você declarar um broadcast receiver no manifesto, o sistema iniciará seu app (se o app ainda não estiver em execução) quando a transmissão for enviada.

Para declarar um broadcast receiver no manifesto, siga as seguintes etapas:

  1. Especifique o elemento <receiver> no manifesto do seu app.

        <receiver android:name=".MyBroadcastReceiver"  android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
            </intent-filter>
        </receiver>
        

    Os filtros de intent especificam as ações de transmissão em que seu receptor está inscrito.

  2. Coloque BroadcastReceiver em uma subclasse e implemente onReceive(Context, Intent). O broadcast receiver no exemplo a seguir registra e exibe o conteúdo da transmissão:

    Kotlin

        private const val TAG = "MyBroadcastReceiver"
    
        class MyBroadcastReceiver : BroadcastReceiver() {
    
            override fun onReceive(context: Context, intent: Intent) {
                StringBuilder().apply {
                    append("Action: ${intent.action}\n")
                    append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
                    toString().also { log ->
                        Log.d(TAG, log)
                        Toast.makeText(context, log, Toast.LENGTH_LONG).show()
                    }
                }
            }
        }
        

    Java

        public class MyBroadcastReceiver extends BroadcastReceiver {
                private static final String TAG = "MyBroadcastReceiver";
                @Override
                public void onReceive(Context context, Intent intent) {
                    StringBuilder sb = new StringBuilder();
                    sb.append("Action: " + intent.getAction() + "\n");
                    sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                    String log = sb.toString();
                    Log.d(TAG, log);
                    Toast.makeText(context, log, Toast.LENGTH_LONG).show();
                }
            }
        

O gerenciador de pacotes do sistema registra o receptor quando o app é instalado. Em seguida, o receptor se torna um ponto de entrada separado para seu app, o que significa que o sistema poderá iniciar o app e entregar a transmissão se o app não estiver em execução no momento.

O sistema cria um novo objeto de componente BroadcastReceiver para gerenciar cada transmissão recebida. Esse objeto é válido apenas pela duração da chamada para onReceive(Context, Intent). Depois que o código retorna desse método, o sistema considera que o componente não está mais ativo.

Receptores registrados pelo contexto

Para registrar um receptor com um contexto, siga as seguintes etapas:

  1. Crie uma instância de BroadcastReceiver.

    Kotlin

        val br: BroadcastReceiver = MyBroadcastReceiver()
        

    Java

        BroadcastReceiver br = new MyBroadcastReceiver();
        

  2. Crie um IntentFilter e registre o receptor chamando registerReceiver(BroadcastReceiver, IntentFilter):

    Kotlin

        val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
            addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
        }
        registerReceiver(br, filter)
        

    Java

        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
            filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
            this.registerReceiver(br, filter);
        

    Receptores registrados pelo contexto recebem transmissões, contanto que o contexto de registro seja válido. Por exemplo, se você se registrar em um contexto Activity, receberá transmissões, desde que a atividade não seja destruída. Se você se registrar com o contexto do aplicativo, receberá transmissões, contanto que o app esteja em execução.

  3. Para parar de receber transmissões, chame unregisterReceiver(android.content.BroadcastReceiver). Cancele o registro do receptor quando não precisar mais dele ou se o contexto não for mais válido.

    Lembre-se do local onde você registra e cancela o registro do receptor. Por exemplo, se registrar um receptor em onCreate(Bundle) usando o contexto da atividade, cancele o registro em onDestroy() para evitar que o receptor vaze do contexto da atividade. Se você registrar um receptor em onResume(), cancele o registro dele em onPause() para evitar registrá-lo várias vezes se não quiser receber transmissões quando pausado. Isso pode reduzir a sobrecarga desnecessária do sistema. Não cancele o registro em onSaveInstanceState(Bundle), porque ele não será chamado se o usuário voltar para a pilha de histórico.

Efeitos no estado do processo

O estado do seu BroadcastReceiver, esteja ou não sendo executado, afeta o estado do processo que o contém, o que pode, por sua vez, afetar a probabilidade de ser eliminado pelo sistema. Por exemplo, quando um processo executa um receptor, isto é, está executando o código no método onReceive(), ele é considerado um processo em primeiro plano. O sistema mantém o processo em execução, exceto em casos de extrema pressão da memória.

No entanto, quando o código retornar de onReceive(), o BroadcastReceiver não estará mais ativo. O processo de host do receptor se torna tão importante quanto os outros componentes do app que estão sendo executados nele. Se esse processo hospedar apenas um receptor declarado pelo manifesto, que é um caso comum para apps com os quais o usuário nunca interagiu ou não interagiu recentemente, ao retornar de onReceive(), o sistema considera o processo como sendo de baixa prioridade e pode eliminá-lo para disponibilizar recursos para outros processos mais importantes.

Por isso, não inicie longas linhas de execução em segundo plano a partir de um broadcast receiver. Depois de onReceive(), o sistema pode eliminar o processo a qualquer momento para recuperar a memória e, ao fazer isso, ele encerra a linha de execução gerada no processo. Para evitar isso, chame goAsync(), se você quiser um pouco mais de tempo para processar a transmissão em uma linha de execução em segundo plano, ou programe um JobService a partir do receptor usando o JobScheduler, de modo que o sistema saiba que o processo continua executando um trabalho ativo. Para mais informações, consulte Processos e ciclo de vida de um app.

O snippet a seguir mostra um BroadcastReceiver que usa goAsync() para sinalizar que precisa de mais tempo para ser finalizado após a conclusão de onReceive(). Isso é especialmente útil se o trabalho que você quer concluir em onReceive() for longo o suficiente para fazer com que a linha de execução de IU perca um frame (> 16 ms), tornando-o mais adequado para uma linha de execução em segundo plano.

Kotlin

    private const val TAG = "MyBroadcastReceiver"

    class MyBroadcastReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            val pendingResult: PendingResult = goAsync()
            val asyncTask = Task(pendingResult, intent)
            asyncTask.execute()
        }

        private class Task(
                private val pendingResult: PendingResult,
                private val intent: Intent
        ) : AsyncTask<String, Int, String>() {

            override fun doInBackground(vararg params: String?): String {
                val sb = StringBuilder()
                sb.append("Action: ${intent.action}\n")
                sb.append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
                return toString().also { log ->
                    Log.d(TAG, log)
                }
            }

            override fun onPostExecute(result: String?) {
                super.onPostExecute(result)
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish()
            }
        }
    }
    

Java

    public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String TAG = "MyBroadcastReceiver";

        @Override
        public void onReceive(Context context, Intent intent) {
            final PendingResult pendingResult = goAsync();
            Task asyncTask = new Task(pendingResult, intent);
            asyncTask.execute();
        }

        private static class Task extends AsyncTask<String, Integer, String> {

            private final PendingResult pendingResult;
            private final Intent intent;

            private Task(PendingResult pendingResult, Intent intent) {
                this.pendingResult = pendingResult;
                this.intent = intent;
            }

            @Override
            protected String doInBackground(String... strings) {
                StringBuilder sb = new StringBuilder();
                sb.append("Action: " + intent.getAction() + "\n");
                sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                String log = sb.toString();
                Log.d(TAG, log);
                return log;
            }

            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish();
            }
        }
    }
    

Como enviar transmissões

O Android oferece três maneiras para os apps enviarem transmissões:

  • O método sendOrderedBroadcast(Intent, String) envia transmissões para um receptor de cada vez. À medida que cada receptor é executado, ele pode propagar um resultado para o próximo receptor ou pode interromper completamente a transmissão para que não seja passada para outros receptores. Os receptores de pedidos executados podem ser controlados com o atributo android:priority do filtro de intent correspondente. Os receptores com a mesma prioridade serão executados em uma ordem arbitrária.
  • O método sendBroadcast(Intent) envia transmissões para todos os receptores em uma ordem indefinida. Isso é chamado de Normal Broadcast. É mais eficiente, mas significa que os receptores não podem ler resultados de outros receptores, propagar dados recebidos ou abortar a transmissão.
  • O método LocalBroadcastManager.sendBroadcast envia transmissões para receptores que estão no mesmo app que o remetente. Se você não precisa enviar transmissões entre apps, use transmissões locais. A implementação é muito mais eficiente porque não é necessária uma comunicação entre processos e você não precisa se preocupar com problemas de segurança relacionados a outros apps conseguindo receber ou enviar suas transmissões.

O snippet de código a seguir demonstra como enviar uma transmissão criando um intent e chamando sendBroadcast(Intent).

Kotlin

    Intent().also { intent ->
        intent.setAction("com.example.broadcast.MY_NOTIFICATION")
        intent.putExtra("data", "Notice me senpai!")
        sendBroadcast(intent)
    }
    

Java

    Intent intent = new Intent();
    intent.setAction("com.example.broadcast.MY_NOTIFICATION");
    intent.putExtra("data","Notice me senpai!");
    sendBroadcast(intent);
    

A mensagem de transmissão é agrupada em um objeto Intent. A string de ação do intent precisa fornecer a sintaxe do nome do pacote Java do app e identificar exclusivamente o evento de transmissão. Você pode anexar outras informações ao intent com putExtra(String, Bundle). Você também pode limitar uma transmissão a um conjunto de apps na mesma organização chamando setPackage(String) no intent.

Como restringir transmissões com permissões

Você pode restringir transmissões ao conjunto de apps que têm determinadas permissões. É possível aplicar restrições para o remetente ou o receptor de uma transmissão.

Como enviar com permissões

Ao chamar sendBroadcast(Intent, String) ou sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle), você poderá especificar um parâmetro de permissão. Somente receptores que solicitaram permissão com a tag no manifesto, e que receberão a permissão se ela for perigosa, poderão receber a transmissão. Por exemplo, o seguinte código envia uma transmissão:

Kotlin

    sendBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)
    

Java

    sendBroadcast(new Intent("com.example.NOTIFY"),
                  Manifest.permission.SEND_SMS);
    

Para receber a transmissão, o app de recebimento precisa solicitar a permissão, conforme mostrado abaixo:

<uses-permission android:name="android.permission.SEND_SMS"/>
    

Você pode especificar uma permissão de sistema existente, como SEND_SMS ou definir uma permissão personalizada com o elemento <permission>. Para mais informações sobre permissões e segurança em geral, consulte as Permissões do sistema.

Como receber com permissões

Se você especificar um parâmetro de permissão ao registrar um broadcast receiver, com registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) ou na tag <receiver> no manifesto, apenas os transmissores que tiverem solicitado a permissão com a tag <uses-permission> no manifesto, e recebido permissão se ela for perigosa, poderão enviar um intent para o receptor.

Por exemplo, suponha que seu app de recebimento tenha um receptor declarado pelo manifesto, conforme mostrado abaixo:

<receiver android:name=".MyBroadcastReceiver"
              android:permission="android.permission.SEND_SMS">
        <intent-filter>
            <action android:name="android.intent.action.AIRPLANE_MODE"/>
        </intent-filter>
    </receiver>
    

Ou o app de recebimento tenha um receptor registrado pelo contexto, conforme mostrado abaixo:

Kotlin

    var filter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
    registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null )
    

Java

    IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );
    

Em seguida, para poder enviar transmissões para esses receptores, o app de envio precisa solicitar a permissão, conforme mostrado abaixo:

<uses-permission android:name="android.permission.SEND_SMS"/>
    

Considerações de segurança e práticas recomendadas

Veja a seguir algumas considerações de segurança e práticas recomendadas para enviar e receber transmissões:

  • Se você não precisa enviar transmissões para componentes fora do seu app, envie e receba transmissões locais com o LocalBroadcastManager que está disponível na Biblioteca de Suporte. O LocalBroadcastManager é muito mais eficiente (sem a necessidade da comunicação entre processos) e permite que você evite problemas de segurança relacionados a outros apps conseguindo receber ou enviar suas transmissões. As transmissões locais podem ser usadas como um barramento de eventos Pub/Sub de objetivo geral no seu app sem sobrecargas de transmissão de todo o sistema.

  • Se muitos apps tiverem se registrado para receber a mesma transmissão no manifesto, o sistema poderá iniciar vários apps, causando um impacto substancial no desempenho do dispositivo e na experiência do usuário. Para evitar isso, prefira usar o registro de contexto na declaração pelo manifesto. Às vezes, o próprio sistema Android impõe o uso de receptores registrados pelo contexto. Por exemplo, a transmissão CONNECTIVITY_ACTION é entregue apenas a receptores registrados pelo contexto.

  • Não transmita informações confidenciais usando um intent implícito. As informações podem ser lidas por qualquer app que se registre para receber a transmissão. Existem três maneiras de controlar quem pode receber suas transmissões:

    • Você pode especificar uma permissão enviando uma transmissão.
    • No Android 4.0 e versões posteriores, você pode especificar um pacote com setPackage(String) enviando uma transmissão. O sistema restringe a transmissão ao conjunto de apps que correspondem ao pacote.
    • Você pode enviar transmissões locais com LocalBroadcastManager.
  • Quando você registra um receptor, qualquer app pode enviar transmissões maliciosas para o receptor do seu app. Existem três maneiras de limitar as transmissões recebidas pelo app:

    • Você pode especificar uma permissão registrando um broadcast receiver.
    • Para receptores declarados pelo manifesto, você pode definir o atributo android:exported como "falso" no manifesto. O receptor não recebe transmissões de fontes externas ao app.
    • Você pode se limitar a apenas transmissões locais com LocalBroadcastManager.
  • O namespace das ações de transmissão é global. Os nomes de ação e outras strings precisam estar escritos em um namespace de sua propriedade, caso contrário, poderá haver um conflito com outros apps.

  • Como o método onReceive(Context, Intent) de um receptor é executado na linha de execução principal, ele precisa ser executado e retornar rapidamente. Se você precisar executar um trabalho de longa duração, tenha cuidado com a geração de linhas de execução ou com a inicialização de serviços em segundo plano, porque o sistema pode eliminar todo o processo depois que onReceive() retornar. Para mais informações, consulte Efeito no estado do processo. Para realizar trabalhos de longa duração, recomendamos:

    • chamar goAsync() no método onReceive() do seu receptor e passar o BroadcastReceiver.PendingResult para uma linha de execução em segundo plano. Isso mantém a transmissão ativa após retornar de onReceive(). No entanto, mesmo com essa abordagem, o sistema espera que você termine a transmissão muito rapidamente, isto é, em menos de 10 segundos. Isso permite que você mova o trabalho para outra linha de execução para evitar que a principal apresente falhas;
    • programar um trabalho com JobScheduler. Para mais informações, consulte Programação inteligente de jobs.
  • Não inicie atividades a partir de broadcast receivers, porque a experiência do usuário é desagradável, especialmente se houver mais de um receptor. Em vez disso, considere a exibição de uma notificação.