Présentation des diffusions

Les applications Android peuvent envoyer ou recevoir des annonces du système Android et d'autres applications Android, de la même manière que le schéma de conception publish-subscribe. Ces annonces sont envoyées lorsqu'un événement d'intérêt se produit. Par exemple, le système Android envoie des annonces lorsque divers événements système se produisent, par exemple lorsque le système démarre ou que l'appareil commence à se charger. Les applications peuvent également envoyer des annonces personnalisées, par exemple pour avertir d'autres applications de quelque chose qui pourrait les intéresser (par exemple, lorsque de nouvelles données ont été téléchargées).

Le système optimise la diffusion des diffusions afin de maintenir un état optimal du système. Par conséquent, les délais de diffusion des diffusions ne sont pas garantis. Les applications nécessitant une communication interprocessus à faible latence doivent envisager des services liés.

Les applications peuvent s'inscrire pour recevoir des annonces spécifiques. Lorsqu'une annonce est envoyée, le système les achemine automatiquement vers les applications qui sont abonnées à ce type de diffusion particulier.

En règle générale, les annonces peuvent être utilisées comme système de messagerie dans les applications et en dehors du parcours utilisateur normal. Toutefois, veillez à ne pas abuser de la possibilité de répondre aux diffusions et à exécuter des tâches en arrière-plan, car cela pourrait ralentir les performances du système.

À propos des annonces système

Le système envoie automatiquement des annonces lorsque divers événements système se produisent, par exemple lorsque le système passe en mode Avion ou passe d'un mode à l'autre. Les diffusions système sont envoyées à toutes les applications abonnées à l'événement.

Le message de diffusion lui-même est encapsulé dans un objet Intent dont la chaîne d'action identifie l'événement qui s'est produit (par exemple, android.intent.action.AIRPLANE_MODE). L'intent peut également inclure des informations supplémentaires regroupées dans son champ supplémentaire. Par exemple, l'intent du mode Avion inclut une valeur booléenne supplémentaire qui indique si le mode Avion est activé ou non.

Pour en savoir plus sur la lecture des intents et l'obtention de la chaîne d'action à partir d'un intent, consultez la section Intents et filtres d'intents.

Pour obtenir la liste complète des actions de diffusion du système, consultez le fichier BROADCAST_ACTIONS.TXT dans le SDK Android. Chaque action de diffusion est associée à un champ constant. Par exemple, la valeur de la constante ACTION_AIRPLANE_MODE_CHANGED est android.intent.action.AIRPLANE_MODE. La documentation de chaque action de diffusion est disponible dans le champ de constante associé.

Modifications apportées aux diffusions système

À mesure que la plate-forme Android évolue, elle modifie régulièrement le comportement des diffusions système. Tenez compte des modifications suivantes pour assurer la compatibilité avec toutes les versions d'Android.

Android 14

Lorsque les applications sont en cache, la diffusion de diffusion est optimisée pour l'état du système. Par exemple, les diffusions système moins importantes telles que ACTION_SCREEN_ON sont différées tant que l'application est mise en cache. Une fois que l'application est passée de l'état mis en cache à un cycle de vie de processus actif, le système diffuse toutes les diffusions différées.

Les diffusions importantes déclarées dans le fichier manifeste suppriment temporairement l'état mis en cache des applications pour les diffuser.

Android 9

À partir d'Android 9 (niveau d'API 28), la diffusion NETWORK_STATE_CHANGED_ACTION ne reçoit plus d'informations sur la position de l'utilisateur ni sur des données permettant d'identifier personnellement l'utilisateur.

De plus, si votre application est installée sur un appareil équipé d'Android 9 ou version ultérieure, les diffusions système à partir du Wi-Fi ne contiennent pas de SSID, de BSSID, d'informations de connexion ni de résultats d'analyse. Pour obtenir ces informations, appelez plutôt getConnectionInfo().

Android 8.0

À partir d'Android 8.0 (niveau d'API 26), le système impose des restrictions supplémentaires aux destinataires déclarés dans le fichier manifeste.

Si votre application cible Android 8.0 ou une version ultérieure, vous ne pouvez pas utiliser le fichier manifeste pour déclarer un récepteur pour la plupart des diffusions implicites (diffusions qui ne ciblent pas spécifiquement votre application). Vous pouvez toujours utiliser un récepteur enregistré en contexte lorsque l'utilisateur utilise activement votre application.

Android 7.0

Android 7.0 (niveau d'API 24) ou version ultérieure n'envoie pas les diffusions système suivantes:

En outre, les applications ciblant Android 7.0 ou version ultérieure doivent enregistrer la diffusion CONNECTIVITY_ACTION à l'aide de registerReceiver(BroadcastReceiver, IntentFilter). La déclaration d'un destinataire dans le fichier manifeste ne fonctionne pas.

Recevoir des annonces

Les applications peuvent recevoir des annonces de deux manières: via des récepteurs déclarés dans le fichier manifeste et via des récepteurs enregistrés dans le contexte.

Destinataires déclarés dans le fichier manifeste

Si vous déclarez un broadcast receiver dans votre fichier manifeste, le système lance votre application (si elle n'est pas déjà en cours d'exécution) lors de l'envoi de l'annonce.

Pour déclarer un broadcast receiver dans le fichier manifeste, procédez comme suit:

  1. Spécifiez l'élément <receiver> dans le fichier manifeste de votre application.

    <!-- 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>
    

    Les filtres d'intent spécifient les actions de diffusion auxquelles votre récepteur s'abonne.

  2. Sous-classe BroadcastReceiver et implémentez onReceive(Context, Intent). Dans les exemples suivants, le broadcast receiver est journalisé et affiche le contenu de l'annonce:

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

    Pour activer la liaison de vue, configurez viewBinding dans le fichier build.gradle au niveau du module.

Le gestionnaire de packages système enregistre le récepteur lorsque l'application est installée. Le récepteur devient alors un point d'entrée distinct dans votre application, ce qui signifie que le système peut démarrer l'application et diffuser la diffusion si l'application n'est pas en cours d'exécution.

Le système crée un objet composant BroadcastReceiver pour gérer chaque diffusion qu'il reçoit. Cet objet n'est valide que pendant la durée de l'appel à onReceive(Context, Intent). Une fois que votre code est renvoyé par cette méthode, le système considère que le composant n'est plus actif.

Destinataires enregistrés en contexte

Les récepteurs enregistrés en contexte reçoivent des annonces tant que leur contexte d'enregistrement est valide. Par exemple, si vous vous enregistrez dans un contexte Activity, vous recevez des annonces tant que l'activité n'est pas détruite. Si vous vous enregistrez dans le contexte de l'application, vous recevez des annonces tant que l'application est en cours d'exécution.

Pour enregistrer un récepteur avec un contexte, procédez comme suit:

  1. Dans le fichier de compilation au niveau du module de votre application, incluez la version 1.9.0 ou ultérieure de la bibliothèque principale AndroidX:

    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-beta01"
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation "androidx.core:core-splashscreen:1.1.0-rc01"
    }
    

    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-beta01")
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation("androidx.core:core-splashscreen:1.1.0-rc01")
    }
    
  2. Créez une instance de BroadcastReceiver:

    Kotlin

    val br: BroadcastReceiver = MyBroadcastReceiver()
    

    Java

    BroadcastReceiver br = new MyBroadcastReceiver();
    
  3. Créez une instance de IntentFilter:

    Kotlin

    val filter = IntentFilter(APP_SPECIFIC_BROADCAST)
    

    Java

    IntentFilter filter = new IntentFilter(APP_SPECIFIC_BROADCAST);
    
  4. Choisissez si le broadcast receiver doit être exporté et visible par les autres applications de l'appareil. Si ce récepteur écoute les annonces envoyées à partir du système ou d'autres applications (même d'autres applications qui vous appartiennent), utilisez l'indicateur RECEIVER_EXPORTED. Si, à la place, ce récepteur n'écoute que les diffusions envoyées par votre application, utilisez l'indicateur 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. Enregistrez le récepteur en appelant registerReceiver():

    Kotlin

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

    Java

    ContextCompat.registerReceiver(context, br, filter, receiverFlags);
    
  6. Pour arrêter de recevoir des annonces, appelez unregisterReceiver(android.content.BroadcastReceiver). Veillez à annuler l'enregistrement du récepteur lorsque vous n'en avez plus besoin ou lorsque le contexte n'est plus valide.

    Faites attention à l'endroit où vous enregistrez et annulez l'enregistrement du récepteur. Par exemple, si vous enregistrez un récepteur dans onCreate(Bundle) à l'aide du contexte de l'activité, vous devez l'annuler dans onDestroy() pour éviter une fuite du récepteur hors du contexte de l'activité. Si vous enregistrez un récepteur dans onResume(), vous devez l'annuler dans onPause() pour éviter de l'enregistrer plusieurs fois (si vous ne souhaitez pas recevoir d'annonces lorsqu'elles sont suspendues, cela peut réduire la surcharge inutile du système). N'annulez pas l'enregistrement dans onSaveInstanceState(Bundle), car cette méthode n'est pas appelée si l'utilisateur revient en arrière dans la pile d'historique.

Effets sur l'état du processus

Le fait que votre BroadcastReceiver fonctionne ou non affecte le processus qu'il contient, ce qui peut modifier sa probabilité d'arrêt du système. Un processus de premier plan exécute la méthode onReceive() d'un récepteur. Le système exécute le processus, sauf en cas de pression extrême sur la mémoire.

BroadcastReceiver est désactivé après onReceive(). Le processus hôte du récepteur est aussi important que ses composants d'application. Si ce processus n'héberge qu'un récepteur déclaré par le fichier manifeste (ce qui arrive fréquemment pour les applications avec lesquelles l'utilisateur n'a jamais interagi récemment), le système peut le fermer après onReceive() afin de rendre les ressources disponibles pour d'autres processus plus critiques.

Ainsi, les broadcast receivers ne doivent pas lancer de threads d'arrière-plan de longue durée. Le système peut arrêter le processus à tout moment après onReceive() pour récupérer de la mémoire, ce qui met fin au thread créé. Pour maintenir le processus actif, planifiez une JobService à partir du récepteur à l'aide de JobScheduler afin que le système sache que le processus fonctionne toujours. Pour plus d'informations, consultez la page Présentation des tâches en arrière-plan.

Envoyer des annonces

Android propose aux applications trois façons d'envoyer une diffusion:

  • La méthode sendOrderedBroadcast(Intent, String) envoie des annonces à un destinataire à la fois. Au fur et à mesure que chaque récepteur s'exécute l'un après l'autre, il peut propager un résultat au destinataire suivant ou peut annuler complètement la diffusion afin qu'elle ne soit pas transmise à d'autres destinataires. L'ordre dans lequel les récepteurs sont exécutés peut être contrôlé avec l'attribut android:Priority de l'intent-filter correspondant. Les récepteurs avec la même priorité sont exécutés dans un ordre arbitraire.
  • La méthode sendBroadcast(Intent) envoie des diffusions à tous les destinataires dans un ordre non défini. C'est ce qu'on appelle une diffusion normale. Cette approche est plus efficace, mais signifie que les destinataires ne peuvent pas lire les résultats d'autres destinataires, propager les données reçues à partir de la diffusion ou l'annuler.

L'extrait de code suivant montre comment envoyer une annonce en créant un intent et en appelant 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);

Le message de diffusion est encapsulé dans un objet Intent. La chaîne d'action de l'intent doit fournir la syntaxe du nom de package Java de l'application et identifier de manière unique l'événement de diffusion. Vous pouvez joindre des informations supplémentaires à l'intent avec putExtra(String, Bundle). Vous pouvez également limiter une diffusion à un ensemble d'applications de la même organisation en appelant setPackage(String) au niveau de l'intent.

Limiter les diffusions à l'aide d'autorisations

Les autorisations vous permettent de limiter les diffusions à l'ensemble d'applications qui détiennent certaines autorisations. Vous pouvez appliquer des restrictions à l'expéditeur ou au destinataire d'une diffusion.

Envoi avec autorisations

Lorsque vous appelez sendBroadcast(Intent, String) ou sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle), vous pouvez spécifier un paramètre d'autorisation. Seuls les destinataires qui ont demandé cette autorisation avec la balise dans leur fichier manifeste (et qui ont ensuite reçu l'autorisation si elle est dangereuse) peuvent recevoir la diffusion. Par exemple, le code suivant envoie une annonce:

Kotlin

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

Java

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

Pour recevoir la diffusion, l'application réceptrice doit demander l'autorisation comme indiqué ci-dessous:

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

Vous pouvez spécifier une autorisation système existante, telle que BLUETOOTH_CONNECT, ou définir une autorisation personnalisée avec l'élément <permission>. Pour plus d'informations sur les autorisations et la sécurité en général, consultez la section Autorisations système.

Recevoir des autorisations

Si vous spécifiez un paramètre d'autorisation lors de l'enregistrement d'un broadcast receiver (avec registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) ou dans la balise <receiver> du fichier manifeste), seuls les diffuseurs qui ont demandé l'autorisation avec la balise <uses-permission> dans leur fichier manifeste (et qui ont reçu l'autorisation par la suite si elle est dangereuse) peuvent envoyer un intent au récepteur.

Par exemple, supposons que votre application réceptrice dispose d'un récepteur déclaré dans le fichier manifeste, comme indiqué ci-dessous:

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

Votre application de réception dispose également d'un récepteur enregistré en contexte, comme indiqué ci-dessous:

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

Ensuite, pour pouvoir envoyer des annonces à ces destinataires, l'application émettrice doit demander l'autorisation, comme indiqué ci-dessous:

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

Considérations de sécurité et bonnes pratiques

Voici quelques considérations de sécurité et bonnes pratiques pour l'envoi et la réception de diffusions:

  • Si de nombreuses applications se sont inscrites pour recevoir la même annonce dans leur fichier manifeste, le système peut lancer un grand nombre d'applications, ce qui aura un impact important sur les performances de l'appareil et l'expérience utilisateur. Pour éviter cela, préférez l'enregistrement du contexte à la déclaration du fichier manifeste. Parfois, le système Android lui-même impose l'utilisation de destinataires enregistrés en contexte. Par exemple, la diffusion CONNECTIVITY_ACTION n'est diffusée qu'aux destinataires enregistrés en contexte.

  • Ne diffusez pas d'informations sensibles à l'aide d'un intent implicite. Ces informations peuvent être lues par n'importe quelle application qui s'inscrit à la diffusion. Trois options s'offrent à vous pour contrôler qui peut recevoir vos annonces:

    • Vous pouvez spécifier une autorisation lorsque vous envoyez une annonce.
    • Sous Android 4.0 et versions ultérieures, vous pouvez spécifier un package avec setPackage(String) lors de l'envoi d'une diffusion. Le système limite la diffusion à l'ensemble d'applications qui correspondent au package.
  • Lorsque vous enregistrez un récepteur, n'importe quelle application peut envoyer des diffusions potentiellement malveillantes au récepteur de votre application. Il existe plusieurs façons de limiter les diffusions reçues par votre application:

    • Vous pouvez spécifier une autorisation lors de l'enregistrement d'un broadcast receiver.
    • Pour les récepteurs déclarés par le fichier manifeste, vous pouvez définir l'attribut android:exported sur "false" dans le fichier manifeste. Le récepteur ne reçoit pas les diffusions provenant de sources extérieures à l'application.
  • L'espace de noms pour les actions de diffusion est global. Assurez-vous que les noms des actions et les autres chaînes sont écrits dans un espace de noms qui vous appartient. Sinon, vous risquez d'entrer en conflit par inadvertance avec d'autres applications.

  • Étant donné que la méthode onReceive(Context, Intent) d'un récepteur s'exécute sur le thread principal, elle doit s'exécuter et renvoyer rapidement. Si vous devez effectuer une tâche de longue durée, faites attention à ne pas générer de threads ou à démarrer des services d'arrière-plan, car le système peut arrêter l'ensemble du processus après le retour de onReceive(). Pour en savoir plus, consultez la section Effet sur l'état du processus. Pour effectuer une tâche de longue durée, nous vous recommandons d'effectuer les actions suivantes:

    • En appelant goAsync() dans la méthode onReceive() du destinataire et en transmettant BroadcastReceiver.PendingResult à un thread d'arrière-plan Cela permet de maintenir la diffusion active après le retour de onReceive(). Cependant, même avec cette approche, le système s'attend à ce que vous terminez la diffusion très rapidement (moins de 10 secondes). Il vous permet de déplacer des tâches vers un autre thread pour éviter les glitchs sur le thread principal.
    • Planifier une tâche avec le JobScheduler Pour en savoir plus, consultez la page Planification intelligente des tâches.
  • Ne démarrez pas d'activités à partir de broadcast receivers, car l'expérience utilisateur est brouillée, en particulier s'il y a plusieurs destinataires. Envisagez plutôt d'afficher une notification.