Descripción general de las transmisiones

Las apps para Android pueden enviar o recibir mensajes de emisión desde el sistema Android y otras apps para Android, de manera 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 transmisiones cuando se producen varios eventos del sistema, como cuando este se inicia o el dispositivo comienza a cargarse. Las apps también pueden enviar transmisiones personalizadas, por ejemplo, para notificar a otras apps sobre algo que podría interesarles (por ejemplo, se descargaron datos nuevos).

El sistema optimiza la entrega de transmisiones para mantener un estado óptimo del sistema. Por lo tanto, los tiempos de entrega de las transmisiones no están garantizados. Aquellas apps que necesitan comunicación entre procesos de baja latencia deben considerar los servicios vinculados.

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

En términos generales, las transmisiones se pueden usar como un sistema de mensajería en todas las apps y fuera del flujo normal de usuarios. Sin embargo, debes tener cuidado de no abusar de la oportunidad de responder a emisiones y ejecutar trabajos en segundo plano que puedan contribuir a un rendimiento lento del sistema.

Acerca de las emisiones del sistema

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

El mensaje de emisión en sí está unido en un objeto Intent cuya cadena de acción identifica el evento que ocurrió (por ejemplo, android.intent.action.AIRPLANE_MODE). El intent también puede incluir información adicional agrupada en su campo adicional. Por ejemplo, el intent de modo de avión incluye un valor booleano adicional que indica si este modo está activado.

Si deseas obtener más información para leer intents y obtener la cadena 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 evoluciona la plataforma de Android, cambia periódicamente el comportamiento de las transmisiones del sistema. Ten en cuenta los siguientes cambios para admitir todas las versiones de Android.

Android 14

Mientras las apps se encuentran en un estado almacenado en caché, la entrega de emisión se optimiza para el estado del sistema. Por ejemplo, las transmisiones menos importantes del sistema, como ACTION_SCREEN_ON, se aplazan mientras la app se encuentra en estado almacenado en caché. Una vez que la app pasa del estado almacenado en caché a un ciclo de vida del proceso activo, el sistema entrega las transmisiones diferidas.

Las transmisiones importantes que se declaran en el manifiesto quitan temporalmente las apps del estado almacenado en caché para la entrega.

Android 9

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

Además, si tu app está instalada en un dispositivo con Android 9 o versiones posteriores, las transmisiones del sistema desde Wi-Fi no contendrán SSID, BSSID, información de conexión ni resultados de análisis. Para obtener esta información, llama a getConnectionInfo() en su lugar.

Android 8.0

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

Si tu app se orienta a Android 8.0 o versiones posteriores, no puedes usar el manifiesto a fin de declarar un receptor para la mayoría de las transmisiones implícitas (transmisiones 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 (nivel de API 24) y las versiones posteriores no envían las siguientes transmisiones del sistema:

Además, las apps que se orientan a Android 7.0 y versiones posteriores deben registrar la transmisión CONNECTIVITY_ACTION con 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: a través de receptores declarados en el manifiesto y 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 se está ejecutando) cuando se envía la transmisió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.

    <!-- If this receiver listens for broadcasts sent from the system or from
         other apps, even other apps that you own, set android:exported to "true". -->
    <receiver android:name=".MyBroadcastReceiver" android:exported="false">
        <intent-filter>
            <action android:name="APP_SPECIFIC_BROADCAST" />
        </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 transmisió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)
    
                    val binding = ActivityNameBinding.inflate(layoutInflater)
                    val view = binding.root
                    setContentView(view)
    
                    Snackbar.make(view, log, Snackbar.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);
    
                ActivityNameBinding binding =
                        ActivityNameBinding.inflate(layoutInflater);
                val view = binding.root;
                setContentView(view);
    
                Snackbar.make(view, log, Snackbar.LENGTH_LONG).show();
            }
        }
    

    Para habilitar la vinculación de vistas, configura viewBinding en el archivo build.gradle del módulo.

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

El sistema crea un nuevo objeto componente BroadcastReceiver para controlar cada transmisión que recibe. Este objeto es válido solo 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

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 que no se destruya la actividad. Si te registras con el contexto de la aplicación, recibirás emisiones mientras se esté ejecutando la app.

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

  1. En el archivo de compilación de nivel de módulo de tu app, incluye la versión 1.9.0 o posterior de la biblioteca de AndroidX Core:

    Groovy

    dependencies {
        def core_version = "1.12.0"
    
        // Java language implementation
        implementation "androidx.core:core:$core_version"
        // Kotlin
        implementation "androidx.core:core-ktx:$core_version"
    
        // To use RoleManagerCompat
        implementation "androidx.core:core-role:1.0.0"
    
        // To use the Animator APIs
        implementation "androidx.core:core-animation:1.0.0-rc01"
        // To test the Animator APIs
        androidTestImplementation "androidx.core:core-animation-testing:1.0.0-rc01"
    
        // Optional - To enable APIs that query the performance characteristics of GMS devices.
        implementation "androidx.core:core-performance:1.0.0"
    
        // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google
        implementation "androidx.core:core-google-shortcuts:1.1.0"
    
        // Optional - to support backwards compatibility of RemoteViews
        implementation "androidx.core:core-remoteviews:1.1.0-alpha01"
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation "androidx.core:core-splashscreen:1.1.0-alpha02"
    }
    

    Kotlin

    dependencies {
        val core_version = "1.12.0"
    
        // Java language implementation
        implementation("androidx.core:core:$core_version")
        // Kotlin
        implementation("androidx.core:core-ktx:$core_version")
    
        // To use RoleManagerCompat
        implementation("androidx.core:core-role:1.0.0")
    
        // To use the Animator APIs
        implementation("androidx.core:core-animation:1.0.0-rc01")
        // To test the Animator APIs
        androidTestImplementation("androidx.core:core-animation-testing:1.0.0-rc01")
    
        // Optional - To enable APIs that query the performance characteristics of GMS devices.
        implementation("androidx.core:core-performance:1.0.0")
    
        // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google
        implementation("androidx.core:core-google-shortcuts:1.1.0")
    
        // Optional - to support backwards compatibility of RemoteViews
        implementation("androidx.core:core-remoteviews:1.1.0-alpha01")
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation("androidx.core:core-splashscreen:1.1.0-alpha02")
    }
    
  2. Crea una instancia de BroadcastReceiver:

    Kotlin

    val br: BroadcastReceiver = MyBroadcastReceiver()
    

    Java

    BroadcastReceiver br = new MyBroadcastReceiver();
    
  3. Crea una instancia de IntentFilter:

    Kotlin

    val filter = IntentFilter(APP_SPECIFIC_BROADCAST)
    

    Java

    IntentFilter filter = new IntentFilter(APP_SPECIFIC_BROADCAST);
    
  4. Elige si el receptor de emisión se debe exportar y ser visible para otras apps del dispositivo. Si este receptor está a la escucha de transmisiones enviadas desde el sistema o desde otras apps (incluso de otras apps de tu propiedad), usa la marca RECEIVER_EXPORTED. En cambio, si este receptor solo escucha transmisiones que envía tu app, usa la marca RECEIVER_NOT_EXPORTED.

    Kotlin

    val listenToBroadcastsFromOtherApps = false
    val receiverFlags = if (listenToBroadcastsFromOtherApps) {
        ContextCompat.RECEIVER_EXPORTED
    } else {
        ContextCompat.RECEIVER_NOT_EXPORTED
    }
    

    Java

    boolean listenToBroadcastsFromOtherApps = false;
    if (listenToBroadcastsFromOtherApps) {
        receiverFlags = ContextCompat.RECEIVER_EXPORTED;
    } else {
        receiverFlags = ContextCompat.RECEIVER_NOT_EXPORTED;
    }
    
  5. Llama a registerReceiver() para registrar el receptor:

    Kotlin

    ContextCompat.registerReceiver(context, br, filter, receiverFlags)
    

    Java

    ContextCompat.registerReceiver(context, br, filter, receiverFlags);
    
  6. Para dejar de recibir emisiones, llama a unregisterReceiver(android.content.BroadcastReceiver). Asegúrate de cancelar el registro del receptor cuando ya no lo necesites o el contexto ya no sea válido.

    Ten en cuenta dónde registras y cancelas el registro del receptor, por ejemplo, si registras un receptor en onCreate(Bundle) con el contexto de la actividad, debes cancelar el registro en onDestroy() para evitar que el receptor se filtre fuera del contexto de la actividad. Si registras un receptor en onResume(), debes cancelar su registro en onPause() para evitar registrarlo varias veces (si no deseas recibir transmisiones cuando se pausan, y esto puede reducir la sobrecarga innecesaria del sistema). No anules el registro en onSaveInstanceState(Bundle), ya que no se lo llamará si el usuario retrocede en la pila del historial.

Efectos en el estado del proceso

Si tu BroadcastReceiver está funcionando o no afecta el proceso contenido, lo que puede alterar la probabilidad de que el sistema finalice. Un proceso en primer plano ejecuta el método onReceive() de un receptor. El sistema ejecuta el proceso, excepto bajo una presión de memoria extrema.

BroadcastReceiver se desactiva después de onReceive(). El proceso del host del receptor es tan significativo como los componentes de su app. Si ese proceso aloja solo un receptor declarado en el manifiesto (un caso frecuente para apps con las que el usuario nunca interactuó o no recientemente), el sistema puede cerrarlo después de onReceive() a fin de que los recursos estén disponibles para otros procesos más críticos.

Por lo tanto, los receptores de emisión no deben iniciar subprocesos en segundo plano de larga duración. El sistema puede detener el proceso en cualquier momento después de onReceive() para recuperar memoria, lo que finaliza el subproceso creado. Para mantener el proceso en funcionamiento, programa un JobService desde el receptor con el JobScheduler, de modo que el sistema sepa que el proceso aún funciona. En Descripción general del trabajo en segundo plano, encontrarás más detalles.

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 a la vez. A medida que se ejecuta cada receptor, se puede propagar un resultado al siguiente o anular la transmisión por completo para que no se pase a otros receptores. El orden en que se ejecutan los receptores se puede controlar con el atributo android:priority del filtro de intents coincidente. Los receptores con la misma prioridad se ejecutarán en un orden arbitrario.
  • El método sendBroadcast(Intent) envía transmisiones a todos los receptores en un orden indefinido. Esto se denomina una transmisión normal. Esto es más eficiente, pero significa que los receptores no pueden leer los resultados de otros receptores, propagar los datos recibidos de la transmisión ni anularla.

En el siguiente fragmento de código, se muestra cómo enviar una transmisión creando un intent y llamando a sendBroadcast(Intent).

Kotlin

Intent().also { intent ->
    intent.setAction("com.example.broadcast.MY_NOTIFICATION")
    intent.putExtra("data", "Nothing to see here, move along.")
    sendBroadcast(intent)
}

Java

Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data", "Nothing to see here, move along.");
sendBroadcast(intent);

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

Cómo restringir emisiones con permisos

Los permisos te permiten restringir emisiones a un conjunto de apps que tienen determinados permisos. Puedes aplicar restricciones en el emisor o el receptor de una transmisió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 los receptores que solicitaron ese permiso con la etiqueta en su manifiesto (y luego se les otorgó el permiso si es peligroso) pueden recibir la transmisión. Por ejemplo, el siguiente código envía una transmisión:

Kotlin

sendBroadcast(Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

Java

sendBroadcast(new Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

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

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

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

Cómo recibir emisiones con permisos

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

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

<receiver android:name=".MyBroadcastReceiver"
          android:permission="android.permission.BLUETOOTH_CONNECT">
    <intent-filter>
        <action android:name="android.intent.action.ACTION_FOUND"/>
    </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_FOUND)
registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null )

Java

IntentFilter filter = new IntentFilter(Intent.ACTION_FOUND);
registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null );

Luego, para poder enviar emisiones a esos receptores, la app emisora debe solicitar el permiso como se muestra a continuación:

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

Consideraciones de seguridad y prácticas recomendadas

A continuación, se incluyen algunas consideraciones de seguridad y prácticas recomendadas para enviar y recibir emisiones:

  • Si muchas apps se registraron para recibir la misma transmisión en su manifiesto, es posible que el sistema inicie muchas apps, lo que produce un impacto significativo en el rendimiento del dispositivo y la experiencia del usuario. Para evitarlo, es preferible que uses el registro de contexto en lugar de la declaración del manifiesto. A veces, el propio sistema Android aplica de manera forzosa el uso de receptores registrados en el contexto. Por ejemplo, la emisión de CONNECTIVITY_ACTION se entrega solo a receptores registrados en el contexto.

  • No emitas información sensible mediante un intent implícito, Cualquier app que se registre para recibir la transmisió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 transmisión. El sistema restringe la transmisión al conjunto de apps que coinciden con el paquete.
  • Cuando registras un receptor, cualquier app puede enviar transmisiones potencialmente maliciosas al receptor de tu app. Existen varias formas de limitar las transmisiones 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 como "false" en el manifiesto. El receptor no recibe transmisiones de fuentes externas a la app.
  • El espacio de nombres para las acciones de emisión es global. Asegúrate de que los nombres de las acciones y otras cadenas estén escritos en un espacio de nombres que te pertenezca. De lo contrario, podrías entrar en conflicto con otras apps de forma involuntaria.

  • 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 un trabajo de larga duración, ten cuidado con la generación de subprocesos o el inicio de servicios en segundo plano, ya que el sistema puede finalizar el proceso completo después de que se devuelve onReceive(). Para obtener más información, consulta Efecto en el estado del proceso. A fin de realizar un trabajo prolongado, te recomendamos 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 transmisión muy rápido (menos de 10 segundos). De esa manera, puedes mover el trabajo a otro subproceso para evitar fallas 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 es molesta, en especial si hay más de un receptor. En su lugar, considera mostrar una notificación.