Gestion des messages broadcast

Le projet d’exemple pour ce chapitre

Vous pouvez télécharger le projet contenant les exemples de ce chapitre : android-demo-app.zip

Nous avons vu que les activités sont lancées à partir de l’émission d’un intent. Un système Android généralise ce principe avec les messages de type broadcast. En informatique, la notion de broadcast désigne la télédiffusion d’une information sans connaissance a priori ni du nombre ni du type de composants qui peuvent traiter cette information.

Un système Android fournit un modèle de publication/souscription pour l’émission et la réception des messages de type broadcast. Un composant peut émettre un message tandis qu’un autre peut s’inscrire pour recevoir ce type de message. Cela permet de créer une système de communication à couplage faible : les émetteurs n’ont pas besoin de connaître les récepteurs (et réciproquement).

Le système Android lui-même émet de nombreux messages en broadcast. Par exemple, si votre application à besoin de se connecter à Internet, elle peut adapter son comportement lorsque le système perd la connexion réseau (mais également lorsque la connexion réseau est rétablie). Pour cela, vos activités peuvent souscrire à la réception de broadcast de type NETWORK_STATE_CHANGED_ACTION pour être prévenue de l’évolution de la connexion Wifi. On trouve de nombreuses utilisations pour la prise en charge de broadcast système : connaître les changements d’état du réseau, être prévenu lorsque la batterie est déchargée, savoir que le téléphone est mis en veille ou sort de veille, être prévenu du démarrage du système ou de son arrêt…

Le BroadcastReceiver

Le BroadcastReceiver est une classe abstraite qui permet de créer un composant en charge de recevoir des broadcasts. Une classe héritant de BroadcastReceiver doit fournir une implémentation de la méthode abstraite onReceive pour traiter le message.

On peut ensuite déclarer le BroadcastReceiver dans le manifeste de l’application pour l’associer à un filtre d’intent, c’est-à-dire pour définir les messages auxquels le récepteur s’inscrit.

Suivre le branchement sur secteur

Prenons l’exemple d’une application qui souhaite être prévenue lorsque le téléphone est branché sur secteur ou bien lorsqu’il passe en mode batterie. Ces changements d’états correspondent à deux broadcasts différents :

  • Pour le branchement sur secteur, l’action est android.intent.action.ACTION_POWER_CONNECTED qui est donnée par la constante ACTION_POWER_CONNECTED

  • Pour le passage sur batterie, l’action est android.intent.action.ACTION_POWER_DISCONNECTED qui est donnée par la constante ACTION_POWER_DISCONNECTED

On peut créer un BroadcastReceiver pour traiter ces deux actions :

Un exemple de BroadcastReceiver
package dev.gayerie.monappli;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class PowerReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
    if (intent.getAction().equals(Intent.ACTION_POWER_CONNECTED)) {
      Toast.makeText(context, "Connecté sur secteur", Toast.LENGTH_LONG).show();
    } else if (intent.getAction().equals(Intent.ACTION_POWER_DISCONNECTED)) {
      Toast.makeText(context, "En mode batterie", Toast.LENGTH_LONG).show();
    }
  }
}

Dans cet exemple simple, l’implementation de PowerReceiver se contente de vérifier l’action correspondant à l’intent reçu en paramètre de la méthode onReceive pour afficher une information à l’utilisateur.

Enfin, pour que le BroadcastReceiver reçoive les messages, il faut le déclarer dans le manifeste de l’application dans la balise <application> :

Déclaration du BroadcastReceiver dans AndroidManifest.xml
<receiver android:name="dev.gayerie.monappli.PowerReceiver">
  <intent-filter>
    <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
    <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
  </intent-filter>
</receiver>

En déclarant le BroadcastReceiver dans le manifeste de l’application, il n’est pas nécessaire que l’application soit démarrée pour que le message soit reçu. Dans ce cas, le système démarre l’application sans aucune activité et se contente d’instancier un objet de la classe PowerReceiver pour appeler sa méthode onReceive. Le premier paramètre de cette méthode correspond au contexte d’exécution du BroadcastReceiver. Il est possible de s’en servir pour appeler la méthode startActivity. Un BroadcastReceiver peut donc effectuer des actions complexes en réponse à un message.

Suivre l’état de la connexion Wifi

Pour une application dépendante de la connexion Internet, il peut être intéressant de réagir au changement d’état de cette connexion. On peut déclarer une BroadcastReceiver pour l’action android.net.wifi.STATE_CHANGE qui est également fournie par la constante NETWORK_STATE_CHANGED_ACTION.

Ci-dessous un exemple d’implémentation d’un BroadcastReceiver :

Un BroadcastReceiver pour suivre les changement d’états de la connexion Wifi
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package dev.gayerie.monappli;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.widget.Toast;

public class WifiReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
    if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
      NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
      if (networkInfo.isAvailable()) {
        Toast.makeText(context, "Connecté au réseau", Toast.LENGTH_LONG).show();
      } else {
        Toast.makeText(context, "Déconnecté du réseau", Toast.LENGTH_LONG).show();
      }
    }
  }
}

Note

À la ligne 15, on récupère un objet de type NetworkInfo en appelant la méthode getParcelableExtra. En effet, un intent peut contenir comme extra des informations complexes (pas uniquement des primitives ou des chaînes de caractères). Pour Android, on parle d’objets parcelables. Il s’agit d’objets qui peuvent être sérialisés de manière à pouvoir être transmis d’une application à l’autre.

On déclare le BroadcastReceiver dans le manifeste de l’application dans la balise <application> :

Déclaration du BroadcastReceiver dans AndroidManifest.xml
<receiver android:name="dev.gayerie.monappli.WifiReceiver">
  <intent-filter>
    <action android:name="android.net.wifi.STATE_CHANGE"/>
  </intent-filter>
</receiver>

Recevoir des broadcasts depuis une activité

La déclaration d’un BroadcastReceiver dans le manifeste de l’application implique que, si nécessaire, l’application sera démarrée (sans activité) et un BroadcastReceiver sera instancié pour traiter le message. Ce n’est pas toujours ce que l’on désire. Imaginons qu’une activité a besoin d’adapter son comportement lorsque le système est ou non en mode hors-connexion (le mode avion). Elle peut consulter cet état mais elle a aussi besoin de savoir si cet état change. Dans ce cas, l’activité a besoin d’un BroadcastReceiver mais uniquement quand elle est affichée. On utilise la méthode registerReceiver pour associer une activité à un BroadcastReceiver. Attention, il ne faut surtout pas oublier d’appeler la méthode unregisterReceiver quand l’activité n’a plus besoin du BroadcastReceiver.

Pour illustrer notre exemple, voici le code source de l’activité.

Un exemple d’activité utilisant un BroadcastReceiver
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package dev.gayerie.monappli;

import androidx.appcompat.app.AppCompatActivity;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.provider.Settings;
import android.widget.TextView;

public class AirplaneActivity extends AppCompatActivity {

  private TextView airplaneModeView;
  private AirplaneModeReceiver airplaneModeReceiver = new AirplaneModeReceiver();

  private class AirplaneModeReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
      setAirplaneModeMessage();
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.getApplication().getApplicationContext();
    setContentView(R.layout.activity_airplane);
    airplaneModeView = findViewById(R.id.airplaneMode);
  }

  @Override
  protected void onResume() {
    super.onResume();
    IntentFilter intentFilter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    registerReceiver(airplaneModeReceiver, intentFilter);
    setAirplaneModeMessage();
  }

  @Override
  protected void onPause() {
    super.onPause();
    unregisterReceiver(airplaneModeReceiver);
  }

  private void setAirplaneModeMessage() {
    if(airplaneModeView != null) {
      airplaneModeView.setText(isAirplaneMode() ? "Mode avion": "Pas mode avion");
    }
  }

  private boolean isAirplaneMode() {
    return Settings.System.getInt(this.getContentResolver(),
                                  Settings.System.AIRPLANE_MODE_ON,
                                  0) != 0;
  }
}

Dans la classe AirplaneActivity, on déclare une classe interne AirplaneModeReceiver qui est un BroadcastReceiver (lignes 18 à 23). On dispose de la méthode setAirplaneModeMessage (ligne 47 à 51) qui permet de mettre à jour l’interface graphique avec un message selon l’état du mode avion. Dans l’activité, on redéfinit la méthode onResume pour mettre à jour l’interface graphique mais également pour enregistrer le BroadcastReceiver (lignes 36-37) en l’associant à un IntentFilter. On redéfinit également la méthode onPause pour être sûr de désenregistrer le BroadcastReceiver (ligne 44) lorsqu’il n’est plus utile à l’activité.

Important

Les méthodes onResume et onPause sont très utiles dans une activité pour activer/désactiver des ressources systèmes comme un BroadcastReceiver.

Émettre un message broadcast

Une application peut elle-même émettre un message broadcast en utilisant la méthode de contexte sendBroadcast. Le message est représenté sous la forme d’un Intent. Il est donc possible de concevoir une application dans laquelle les différents composants peuvent s’échanger des informations sur le modèle de la publication/souscription.

Emission d’un message depuis une activité
Intent intent = new Intent();
intent.setAction("dev.gayerie.monappli.MON_MESSAGE");
// on ajoute éventuellement des extras
intent.putExtra("info","Ceci est une info accompagnant le message");
// émission du message
this.sendBroadcast(intent);

Lorsque les messages émis sont à destination uniquement de composants de notre application, il est possible d’utiliser le LocalBroadcastManager qui permet de limiter la diffusion de messages à l’application elle-même.

Emission d’un message local à l’application depuis une activité
Intent intent = new Intent();
intent.setAction("dev.gayerie.monappli.MON_MESSAGE");
// on ajoute éventuellement des extras
intent.putExtra("info","Ceci est une info accompagnant le message");
// émission locale à l'application
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

Note

Dans le manifeste de l’application, pour limiter un BroadcastReceiver à la réception de messages locaux, on utilise l’attribut android:exported avec la valeur false dans la déclaration.

Déclaration d’un BroadcastReceiver local
<receiver android:name="dev.gayerie.monappli.MyLocalReceiver"
          android:exported="false">
  <intent-filter>
    <action android:name="dev.gayerie.monappli.MON_MESSAGE"/>
  </intent-filter>
</receiver>