Descripción general de las transmisiones

Las apps para Android envían y reciben mensajes de emisión desde el sistema Android y otras apps para Android, de forma similar al patrón de diseño de publicación y suscripción. Por lo general, el sistema y las apps envían emisiones cuando ocurren ciertos eventos. Por ejemplo, el sistema Android envía emisiones cuando ocurren diferentes eventos del sistema, como el inicio del sistema o la carga del dispositivo. Las apps también envían emisiones personalizadas, por ejemplo, para notificar a otras apps sobre algo que podría interesarles (como la descarga de 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.

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. Todas las apps suscriptas reciben estas emisiones.

El objeto Intent contiene el mensaje de emisión. La cadena action identifica el evento que ocurrió, como 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 cadena de acción de un intent, consulta Intents y filtros de intents.

Acciones de transmisión del sistema

Para obtener una lista completa de las acciones de transmisió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 para admitir todas las versiones de Android.

Android 16

En Android 16, no se garantizará el orden de entrega de la emisión con el android:priority atributo o IntentFilter.setPriority() en diferentes procesos. Las prioridades de emisión solo se respetan dentro del mismo proceso de aplicación, en lugar de en todos los procesos.

Además, las prioridades de emisión se limitan automáticamente al rango (SYSTEM_LOW_PRIORITY + 1, SYSTEM_HIGH_PRIORITY - 1). Solo los componentes del sistema pueden establecer SYSTEM_LOW_PRIORITY y SYSTEM_HIGH_PRIORITY como prioridad de emisión.

Android 14

Mientras las apps están en un estado almacenado en caché, el sistema optimiza la entrega de emisiones para el estado del sistema. Por ejemplo, el sistema difiere las emisiones del sistema menos importantes, como ACTION_SCREEN_ON mientras la app está en un estado almacenado en caché. Una vez que la app pasa del estado almacenado en caché a un ciclo de vida de proceso activo, el sistema entrega cualquier emisión diferida.

Las emisiones 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 NETWORK_STATE_CHANGED_ACTION emisión no recibe información sobre la ubicación del usuario ni los datos de carácter personal.

Si tu app está instalada en un dispositivo que ejecuta Android 9.0 (API nivel 28) o una versión posterior, el sistema no incluye SSID, BSSID, información de conexión ni resultados de análisis en las emisiones de Wi-Fi. 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 una versión posterior, no puedes usar el manifiesto para 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 (nivel de API 24) y las versiones posteriores no envían las siguientes transmisiones del sistema:

Además, las apps orientadas a Android 7.0 y versiones posteriores deben registrar la CONNECTIVITY_ACTION emisión 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: mediante receptores registrados en el contexto y receptores declarados en el manifiesto.

Receptores registrados en el contexto

Los receptores registrados en el contexto reciben emisiones siempre que su contexto de registro sea válido. Por lo general, esto ocurre entre las llamadas a registerReceiver y unregisterReceiver. El contexto de registro también deja de ser válido cuando el sistema destruye el contexto correspondiente. Por ejemplo, si te registras en un Activity contexto, recibirás emisiones siempre y cuando la actividad permanezca activa. Si te registras con el contexto de la aplicación, recibirás emisiones mientras la app esté en ejecución.

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

  1. En el archivo de compilación a nivel del 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.19.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.1.0"
    
        // To use the Animator APIs
        implementation "androidx.core:core-animation:1.0.0"
        // To test the Animator APIs
        androidTestImplementation "androidx.core:core-animation-testing:1.0.0"
    
        // 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"
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation "androidx.core:core-splashscreen:1.2.0"
    }

    Kotlin

    dependencies {
        val core_version = "1.19.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.1.0")
    
        // To use the Animator APIs
        implementation("androidx.core:core-animation:1.0.0")
        // To test the Animator APIs
        androidTestImplementation("androidx.core:core-animation-testing:1.0.0")
    
        // 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")
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation("androidx.core:core-splashscreen:1.2.0")
    }
  2. Crea una instancia de BroadcastReceiver:

    Kotlin

    val myBroadcastReceiver = MyBroadcastReceiver()
    

    Java

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

    Kotlin

    val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA")
    

    Java

    IntentFilter filter = new IntentFilter("com.example.snippets.ACTION_UPDATE_DATA");
    
  4. Elige si el receptor de transmisiones debe exportarse y ser visible para otras apps en el dispositivo. Si este receptor está escuchando emisiones enviadas desde el sistema o desde otras apps (incluso otras apps de tu propiedad), usa la marca RECEIVER_EXPORTED. Si, en cambio, este receptor solo escucha las emisiones enviadas por 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;
    int receiverFlags = listenToBroadcastsFromOtherApps
            ? ContextCompat.RECEIVER_EXPORTED
            : ContextCompat.RECEIVER_NOT_EXPORTED;
    
  5. Registra el receptor con una llamada a registerReceiver():

    Kotlin

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

    Java

    ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags);
    
  6. 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.

Anula el registro de tu receptor de emisión

Mientras el receptor de transmisiones esté registrado, tendrá una referencia al contexto con el que lo registraste. Esto puede provocar pérdidas si el alcance registrado del receptor supera el alcance del ciclo de vida del contexto. Por ejemplo, esto puede ocurrir cuando registras un receptor en un alcance de actividad, pero olvidas anular su registro cuando el sistema destruye la actividad. Por lo tanto, siempre cancela el registro de tu receptor de transmisiones.

Kotlin

class MyActivity : ComponentActivity() {
    private val myBroadcastReceiver = MyBroadcastReceiver()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags)
        setContent { MyApp() }
    }

    override fun onDestroy() {
        super.onDestroy()
        // When you forget to unregister your receiver here, you're causing a leak!
        this.unregisterReceiver(myBroadcastReceiver)
    }
}

Java

class MyActivity extends ComponentActivity {
    MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ...
        ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags);
        // Set content
    }
}

Registra receptores en el alcance más pequeño

Tu receptor de transmisiones solo debe registrarse cuando realmente te interese el resultado. Elige el alcance del receptor más pequeño posible:

  • LifecycleResumeEffect o métodos del ciclo de vida onResume/onPause de la actividad: El receptor de transmisiones solo recibe actualizaciones mientras la app está en estado reanudado.
  • LifecycleStartEffect o métodos del ciclo de vida onStart/onStop de la actividad: El receptor de transmisiones solo recibe actualizaciones mientras la app está en estado reanudado.
  • DisposableEffect: El receptor de transmisiones solo recibe actualizaciones mientras el elemento componible está en el árbol de Composición. Este alcance no está adjunto al alcance del ciclo de vida de la actividad. Considera registrar el receptor en el contexto de la aplicación. Esto se debe a que el elemento componible podría, en teoría, sobrevivir al alcance del ciclo de vida de la actividad y perder la actividad.
  • Actividad onCreate/onDestroy: El receptor de transmisiones recibe actualizaciones mientras la actividad está en estado creado. Asegúrate de anular el registro en onDestroy() y no en onSaveInstanceState(Bundle), ya que es posible que no se llame a este.
  • Un alcance personalizado: Por ejemplo, puedes registrar un receptor en tu alcance ViewModel para que sobreviva a la recreación de la actividad. Asegúrate de usar el contexto de la aplicación para registrar el receptor, ya que el receptor puede sobrevivir al alcance del ciclo de vida de la actividad y perder la actividad.

Crea elementos componibles con y sin estado

Compose tiene elementos componibles con y sin estado. Registrar o anular el registro de un receptor de transmisiones dentro de un elemento componible lo hace con estado. El elemento componible no es una función determinista que renderiza el mismo contenido cuando se le pasan los mismos parámetros. El estado interno puede cambiar según las llamadas al receptor de emisión registrado.

Como práctica recomendada en Compose, te sugerimos que dividas tus elementos componibles en versiones con y sin estado. Por lo tanto, te recomendamos que eleves la creación del receptor de transmisiones fuera de un elemento componible para dejarlo sin estado:

@Composable
fun MyStatefulScreen() {
    val myBroadcastReceiver = remember { MyBroadcastReceiver() }
    val context = LocalContext.current
    LifecycleStartEffect(true) {
        // ...
        ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, flags)
        onStopOrDispose { context.unregisterReceiver(myBroadcastReceiver) }
    }
    MyStatelessScreen()
}

@Composable
fun MyStatelessScreen() {
    // Implement your screen
}

Receptores declarados en el manifiesto

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

Para declarar un receptor de transmisiones en el manifiesto, realiza los siguientes pasos:

  1. Especifica el <receiver> elemento 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="com.example.snippets.ACTION_UPDATE_DATA" />
        </intent-filter>
    </receiver>
    

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

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

    Kotlin

    class MyBroadcastReceiver : BroadcastReceiver() {
    
        @Inject
        lateinit var dataRepository: DataRepository
    
        override fun onReceive(context: Context, intent: Intent) {
            if (intent.action == "com.example.snippets.ACTION_UPDATE_DATA") {
                val data = intent.getStringExtra("com.example.snippets.DATA") ?: "No data"
                // Do something with the data, for example send it to a data repository:
                dataRepository.updateData(data)
            }
        }
    }
    

    Java

    public static class MyBroadcastReceiver extends BroadcastReceiver {
    
        @Inject
        DataRepository dataRepository;
    
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Objects.equals(intent.getAction(), "com.example.snippets.ACTION_UPDATE_DATA")) {
                String data = intent.getStringExtra("com.example.snippets.DATA");
                // Do something with the data, for example send it to a data repository:
                if (data != null) { dataRepository.updateData(data); }
            }
        }
    }
    

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 BroadcastReceiver objeto componente para controlar cada emisión que recibe. Este objeto es válido solamente durante la duración de 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.

Efectos en el estado del proceso

El hecho de que tu BroadcastReceiver esté en funcionamiento o no afecta el proceso que contiene, lo que puede alterar la probabilidad de que el sistema lo elimine. Un proceso en primer plano ejecuta el método onReceive() de un receptor. El sistema ejecuta el proceso, excepto en casos de extrema presión de la memoria.

El sistema desactiva el BroadcastReceiver después de onReceive(). La importancia del proceso de host del receptor depende de los componentes de la app. Si ese proceso aloja solo un receptor declarado en el manifiesto, el sistema podría eliminarlo después de onReceive() para liberar recursos para otros procesos más importantes. Esto es común para las apps con las que el usuario nunca interactuó o lo hizo recientemente.

Por lo tanto, los receptores de emisión no deben iniciar subprocesos prolongados en segundo plano. El sistema puede detener el proceso en cualquier momento después de onReceive() para reclamar la memoria y finalizar el subproceso creado. Para mantener el proceso activo, programa un JobService desde el receptor con el JobScheduler para que el sistema sepa que el proceso aún está funcionando. En Descripción general del trabajo en segundo plano , se proporcionan más detalles.

Cómo enviar emisiones

Android ofrece dos 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. También puede anular por completo la emisión para que no llegue a otros receptores. Puedes controlar el orden en el que se ejecutan los receptores dentro del mismo proceso de la app. Para hacerlo, usa el atributo android:priority del filtro de intent coincidente. Los receptores con la misma prioridad se ejecutan 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.

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

val intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply {
    putExtra("com.example.snippets.DATA", newData)
    setPackage("com.example.snippets")
}
context.sendBroadcast(intent)

Java

Intent intent = new Intent("com.example.snippets.ACTION_UPDATE_DATA");
intent.putExtra("com.example.snippets.DATA", newData);
intent.setPackage("com.example.snippets");
context.sendBroadcast(intent);

El mensaje de emisión está contenido en un Intent objeto. La cadena action 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 <uses-permission> en su manifiesto. Si el permiso es peligroso, debes otorgarlo antes de que el receptor pueda recibir la emisión. Por ejemplo, el siguiente código envía una emisión con un permiso:

Kotlin

context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION)

Java

context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION);

Para recibir la emisión, la app receptora debe solicitar el permiso de la siguiente manera:

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

Puedes especificar un permiso existente del sistema como BLUETOOTH_CONNECT o definir un permiso personalizado con el <permission> elemento. 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 transmisiones (ya sea con registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) o en <receiver> etiqueta en tu manifiesto), solo los emisores que han solicitado el permiso con la <uses-permission> etiqueta en su manifiesto pueden enviar un intent al receptor. Si el permiso es peligroso, también se debe otorgar al emisor.

Por ejemplo, supongamos que tu app receptora tiene un receptor declarado en el manifiesto de la siguiente manera:

<!-- 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=".MyBroadcastReceiverWithPermission"
    android:permission="android.permission.ACCESS_COARSE_LOCATION"
    android:exported="true">
    <intent-filter>
        <action android:name="com.example.snippets.ACTION_UPDATE_DATA" />
    </intent-filter>
</receiver>

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

Kotlin

ContextCompat.registerReceiver(
    context, myBroadcastReceiver, filter,
    android.Manifest.permission.ACCESS_COARSE_LOCATION,
    null, // scheduler that defines thread, null means run on main thread
    receiverFlags
)

Java

ContextCompat.registerReceiver(
        context, myBroadcastReceiver, filter,
        android.Manifest.permission.ACCESS_COARSE_LOCATION,
        null, // scheduler that defines thread, null means run on main thread
        receiverFlags
);

Luego, para poder enviar emisiones a esos receptores, la app que las envía debe solicitar el permiso de la siguiente manera:

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

Consideraciones de seguridad

Estas son algunas consideraciones de seguridad para enviar y recibir emisiones:

  • 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 (nivel de API 14) 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.
  • Cuando registras un receptor, cualquier app puede enviar emisiones potencialmente maliciosas al receptor de tu app. Existen varias maneras de limitar las emisiones que recibe tu app:

    • Puedes especificar un permiso cuando registras un receptor de transmisiones.
    • 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.
  • 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 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. Para 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 para 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.