Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Descripción general de las transmisiones

Las apps de Android pueden enviar o recibir mensajes de emisión desde el sistema de Android y otras apps para Android, de forma similar al patrón de diseño de publicación y suscripción. Estas emisiones se envían cuando ocurre un evento de interés. Por ejemplo, el sistema Android envía emisiones cuando ocurren diferentes eventos del sistema, como cuando este se inicia o cuando el dispositivo comienza a cargarse. Las apps también pueden enviar emisiones personalizadas, por ejemplo, para notificar a otras apps sobre algo que podría interesarles (como cuando se descargaron algunos datos nuevos).

Las apps pueden registrarse para recibir emisiones específicas. Cuando se envía una emisión, el sistema redirige automáticamente las emisiones a las apps que se suscribieron para recibir ese tipo de emisión particular.

Por lo general, las emisiones pueden usarse como un sistema de mensajería entre apps y fuera del flujo de usuarios normal. Sin embargo, debes tener cuidado de no abusar de la oportunidad de responder a las emisiones y ejecutar tareas en segundo plano que puedan contribuir a ralentizar el rendimiento del sistema, como se explica en el siguiente video.

Acerca de las emisiones del sistema

El sistema envía emisiones automáticamente cuando se producen diferentes eventos del sistema, como cuando este activa o desactiva el modo de avión. Las emisiones del sistema se envían a todas las apps que se suscribieron para recibir el evento.

El mensaje de emisión en sí está envuelto en un objeto Intent cuya string de acción identifica el evento que ocurrió (por ejemplo, android.intent.action.AIRPLANE_MODE). El intent también puede contener información adicional incluida en su campo adicional. Por ejemplo, el intent del modo de avión incluye un valor booleano adicional que indica si el modo está activado o no.

Para obtener más información sobre cómo leer los intents y obtener la string de acción de un intent, consulta Intents y filtros de intents.

Para obtener una lista completa de las acciones de emisión del sistema, consulta el archivo BROADCAST_ACTIONS.TXT en el SDK de Android. Cada acción de emisión tiene un campo constante asociado. Por ejemplo, el valor de la constante ACTION_AIRPLANE_MODE_CHANGED es android.intent.action.AIRPLANE_MODE. La documentación de cada acción de emisión está disponible en su campo constante asociado.

Cambios en las emisiones del sistema

A medida que la plataforma de Android evoluciona, cambia periódicamente el comportamiento de las emisiones del sistema. Ten en cuenta los siguientes cambios si tu app se orienta a Android 7.0 (API nivel 24) o una versión posterior, o si está instalada en dispositivos con Android 7.0 o una versión posterior.

Android 9

A partir de Android 9 (API nivel 28), la emisión NETWORK_STATE_CHANGED_ACTION no recibe información sobre la ubicación del usuario ni los datos de carácter personal.

Además, si tu app está instalada en un dispositivo que ejecuta Android 9 o una versión posterior, las emisiones del sistema desde Wi-Fi no contienen SSID, BSSID, información de conexión ni resultados de análisis. Para obtener esta información, en su lugar, llama a getConnectionInfo().

Android 8.0

A partir de Android 8.0 (API nivel 26), el sistema impone restricciones adicionales a los receptores declarados en el manifiesto.

Si tu app se orienta a Android 8.0 o una versión posterior, no puedes usar el manifiesto a fin de declarar un receptor para la mayoría de las emisiones implícitas (emisiones que no se orientan específicamente a tu app). Puedes usar un receptor registrado en el contexto cuando el usuario usa tu app de forma activa.

Android 7.0

Android 7.0 (API nivel 24) y las versiones posteriores no envían las siguientes emisiones del sistema:

Además, las apps que se orientan a Android 7.0 y versiones posteriores deben registrar la emisión de CONNECTIVITY_ACTION mediante registerReceiver(BroadcastReceiver, IntentFilter). La declaración de un receptor en el manifiesto no funciona.

Cómo recibir emisiones

Las apps pueden recibir emisiones de dos maneras: mediante receptores declarados en el manifiesto y mediante receptores registrados en el contexto.

Receptores declarados en el manifiesto

Si declaras un receptor de emisión en tu manifiesto, el sistema inicia la app (si aún no está en ejecución) cuando se envía la emisión.

Para declarar un receptor de emisión en el manifiesto, realiza los siguientes pasos:

  1. Especifica el elemento <receiver> en el manifiesto de tu 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>
        

    Los filtros de intents especifican las acciones de emisión a las que se suscribe tu receptor.

  2. Crea la subclase BroadcastReceiver y, luego, implementa onReceive(Context, Intent). El receptor de emisión del siguiente ejemplo registra y muestra el contenido de la emisión:

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

El administrador de paquetes del sistema registra el receptor cuando se instala la app. Luego, el receptor se convierte en un punto de entrada a tu app independiente, lo que significa que el sistema puede iniciar la app y entregar la emisión si esta no está en ejecución.

A continuación, el sistema crea un nuevo objeto componente BroadcastReceiver para controlar cada emisión que recibe. Este objeto es válido solamente durante la llamada a onReceive(Context, Intent). Una vez que este método muestra tu código, el sistema considera que el componente ya no está activo.

Receptores registrados en el contexto

Para registrar un receptor con un contexto, realiza los siguientes pasos:

  1. Crea una instancia de BroadcastReceiver.

    Kotlin

        val br: BroadcastReceiver = MyBroadcastReceiver()
        

    Java

        BroadcastReceiver br = new MyBroadcastReceiver();
        

  2. Crea un IntentFilter y registra el receptor mediante una llamada a 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);
        

    Los receptores registrados en el contexto reciben emisiones siempre que su contexto de registro sea válido. Por ejemplo, si te registras en un contexto Activity, recibirás emisiones siempre y cuando no se elimine la actividad. Si te registras con el contexto de la aplicación, recibirás emisiones mientras la app esté en ejecución.

  3. Para dejar de recibir emisiones, llama a unregisterReceiver(android.content.BroadcastReceiver). Asegúrate de anular el registro del receptor cuando ya no lo necesites o el contexto ya no sea válido.

    Recuerda dónde registras y anulas el registro del receptor; por ejemplo, si lo registras en onCreate(Bundle) mediante el contexto de la actividad, debes anular el registro en onDestroy() a fin de evitar que el receptor salga del contexto de la actividad. Si registras un receptor en onResume(), debes anular el registro en onPause() a fin de evitar registrarlo varias veces (si no deseas recibir emisiones cuando está detenido, lo que puede reducir gastos innecesarios del sistema). No anules el registro en onSaveInstanceState(Bundle), porque no se llama a este si el usuario retrocede en la pila del historial.

Efectos en el estado del proceso

El estado de tu BroadcastReceiver (ya sea que esté en ejecución o no) afecta el estado del proceso que lo contiene, lo que podría afectar la probabilidad de que el sistema lo elimine. Por ejemplo, cuando un proceso ejecuta un receptor (es decir, actualmente ejecuta el código en su método onReceive()), se considera que es un proceso en primer plano. El sistema mantiene el proceso en ejecución, excepto en casos de extrema presión de la memoria.

Sin embargo, una vez que se muestra tu código desde onReceive(), el BroadcastReceiver ya no está activo. El proceso de alojamiento del receptor se vuelve tan importante como los demás componentes de la app que se ejecutan en él. Si ese proceso aloja solo un receptor declarado en el manifiesto (un caso común para apps con las que el usuario nunca interactuó o lo hizo recientemente), cuando se muestra desde onReceive(), el sistema considera que su proceso es de baja prioridad y puede eliminarlo a fin de que los recursos estén disponibles para otros procesos más importantes.

Por este motivo, no debes comenzar a ejecutar subprocesos prolongados en segundo plano desde un receptor de emisión. Después de onReceive(), el sistema puede eliminar el proceso en cualquier momento a fin de reclamar la memoria y, al hacerlo, finaliza el subproceso generado que se ejecuta en el proceso. A fin de evitarlo, debes llamar a goAsync() (si deseas un poco más de tiempo para procesar la emisión en un subproceso en segundo plano) o programar un JobService desde el receptor por medio del JobScheduler de manera que el sistema sepa que el proceso continúa trabajando activamente Para obtener más información, consulta Ciclo de vida de procesos y aplicaciones.

En el siguiente fragmento, se muestra un BroadcastReceiver que usa goAsync() a fin de marcar que necesita más tiempo para terminar una vez que se completa onReceive(), lo cual resulta especialmente útil si la tarea que deseas completar en tu onReceive() es lo suficientemente extensa como para que el subproceso de IU pierda un fotograma (>16 ms), lo que la hace más adecuada para un subproceso en 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();
            }
        }
    }
    

Cómo enviar emisiones

Android ofrece tres maneras para que las apps envíen emisiones:

  • El método sendOrderedBroadcast(Intent, String) envía emisiones a un receptor por vez. Como se ejecuta un receptor por vez, este puede propagar un resultado al siguiente o puede anular por completo la emisión de manera que no se transmita a otros. El orden en el que se ejecutan los receptores puede controlarse con el atributo android:priority del filtro de intent coincidente; los receptores con la misma prioridad se ejecutarán en orden aleatorio.
  • El método sendBroadcast(Intent) envía emisiones a todos los receptores en un orden no especificado, lo que se denomina emisión normal. Este método es más eficiente, pero implica que los receptores no pueden leer los resultados de otros receptores, propagar los datos recibidos de la emisión ni anular la emisión.
  • El método LocalBroadcastManager.sendBroadcast envía emisiones a los receptores que están en la misma app que el emisor. Si no necesitas enviar emisiones entre apps, usa emisiones locales. La implementación es mucho más eficiente (no se requiere comunicación entre procesos) y no tienes que preocuparte por ningún problema de seguridad relacionado con otras apps que puedan recibir o enviar emisiones.

En el siguiente fragmento de código, se muestra cómo enviar una emisión mediante la creación de un intent y una llamada a 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);
    

El mensaje de emisión está envuelto en un objeto Intent. La string de acción del intent debe proporcionar la sintaxis del nombre del paquete Java de la app y, además, identificar de forma exclusiva el evento de emisión. Puedes adjuntar información adicional al intent con putExtra(String, Bundle). También puedes limitar una emisión a un conjunto de apps en la misma organización al llamar a setPackage(String) en el intent.

Cómo restringir emisiones con permisos

Los permisos te permiten restringir emisiones a un conjunto de apps que cuenta con permisos específicos. Puedes aplicar restricciones tanto en el emisor como en el receptor de una emisión.

Cómo enviar emisiones con permisos

Cuando llamas a sendBroadcast(Intent, String) o sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle), puedes especificar un parámetro de permiso. Solo pueden recibir la emisión los receptores que solicitaron ese permiso con la etiqueta en su manifiesto (y a los que posteriormente se les otorgó el permiso si es peligroso). Por ejemplo, el siguiente código envía una emisión:

Kotlin

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

Java

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

Para recibir la emisión, la app receptora debe solicitar el permiso como se indica a continuación:

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

Puedes especificar un permiso existente del sistema como SEND_SMS o definir un permiso personalizado con el elemento <permission>. Para obtener información sobre los permisos y la seguridad en general, consulta los Permisos del sistema.

Cómo recibir emisiones con permisos

Si especificas un parámetro de permisos cuando registras un receptor de emisión (ya sea con registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) o en la etiqueta <receiver> en tu manifiesto), solo los emisores que solicitaron el permiso con la etiqueta <uses-permission> en su manifiesto (y a los que posteriormente se les otorgó el permiso si es peligroso) pueden enviar un intent al receptor.

Por ejemplo, supongamos que tu app receptora tiene un receptor declarado en el manifiesto como se muestra a continuación:

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

O bien la app receptora tiene un receptor registrado en el contexto, de la siguiente manera:

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

Luego, para poder enviar emisiones a esos receptores, la app que las envía debe solicitar el permiso como se indica a continuación:

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

Consideraciones de seguridad y prácticas recomendadas

Estas son algunas consideraciones de seguridad y las prácticas recomendadas para enviar y recibir emisiones:

  • Si no necesitas enviar emisiones a componentes fuera de tu app, puedes enviar y recibir emisiones locales con el LocalBroadcastManager que está disponible en la biblioteca de compatibilidad. El LocalBroadcastManager es mucho más eficiente (no se requiere comunicación entre procesos) y te permite evitar problemas de seguridad relacionados con otras apps que pueden recibir o enviar tus emisiones. Las emisiones locales pueden usarse como un bus de eventos de publicación y suscripción de uso general en tu app sin ningún tipo de sobrecarga de emisiones en todo el sistema.

  • Si se registraron muchas apps para recibir la misma emisión en su manifiesto, es posible que el sistema inicie muchas apps, lo que afecta considerablemente el rendimiento del dispositivo y la experiencia del usuario. Si quieres evitarlo, debes usar el registro de contexto en lugar de la declaración en el manifiesto. A veces, el propio sistema Android impone el uso de receptores registrados en el contexto. Por ejemplo, la emisión de CONNECTIVITY_ACTION se envía solamente a receptores registrados en el contexto.

  • No emitas información sensible mediante un intent implícito, ya que cualquier app que se registre para recibir la emisión puede leer la información. Existen tres maneras de controlar quién puede recibir tus emisiones:

    • Puedes especificar un permiso cuando envías una emisión.
    • En Android 4.0 y versiones posteriores, puedes especificar un paquete con setPackage(String) cuando envías una emisión. El sistema restringe la emisión al conjunto de apps que coinciden con el paquete.
    • Puedes enviar emisiones locales con LocalBroadcastManager.
  • Cuando registras un receptor, cualquier app puede enviar emisiones potencialmente maliciosas al receptor de tu app. Existen tres maneras de limitar las emisiones que recibe tu app:

    • Puedes especificar un permiso cuando registras un receptor de emisión.
    • En el caso de los receptores declarados en el manifiesto, puedes establecer el atributo android:exported en "false" en el manifiesto. El receptor no recibe emisiones de fuentes externas a la app.
    • Puedes limitarte solo a emisiones locales con LocalBroadcastManager.
  • El espacio de nombres para las acciones de emisión es global. Asegúrate de que los nombres de las acciones y otras strings estén escritos en un espacio de nombres del cual seas propietario; de lo contrario, podría generarse un conflicto con otras apps accidentalmente.

  • Como el método onReceive(Context, Intent) de un receptor se ejecuta en el subproceso principal, debería ejecutarse y mostrarse rápidamente. Si necesitas realizar una tarea prolongada, sé cuidadoso al generar subprocesos o al iniciar servicios en segundo plano, ya que el sistema podría eliminar todo el proceso después de que se muestre onReceive(). Para obtener más información, consulta Efectos en el estado del proceso. A fin de realizar una tarea prolongada, te recomendamos que hagas lo siguiente:

    • Llama a goAsync() en el método onReceive() de tu receptor y pasa BroadcastReceiver.PendingResult a un subproceso en segundo plano. De esta manera, la emisión se mantiene activa luego de que se muestra desde onReceive(). Sin embargo, incluso con este enfoque, el sistema espera que termines la emisión rápidamente (menos de 10 segundos). De igual manera, te permite mover la tarea a otro subproceso a fin de evitar que se produzca un error en el subproceso principal.
    • Programa una tarea con JobScheduler. Para obtener más información, consulta Programación inteligente de tareas.
  • No inicies actividades desde receptores de emisión porque la experiencia del usuario no es coherente, en especial, si hay varios receptores. En su lugar, considera mostrar una notificación.