Push, du nouveau avec GCM et notifications stylées sur Android 4.1

Il y a quelques semaines, alors que nous développions un mécanisme de notifications push pour l’un de nos clients, Google annonce de gros changements sur son service de push ainsi que des améliorations intéressantes sur les notifications Android. Autant le second n’avait pas d’impact sur nos développements, autant l’annonce de la fin de C2DM nous a quelque peu effrayé... Nous vous proposons un retour d’expérience sur la migration et les nouveautés apportées par Google.

C2DM est mort, vive GCM

"GCM"

Google a très bien fait les choses avec son nouveau service : Google Cloud Messaging (GCM). Nous avons noté plusieurs points très positifs :

  • Plus de quotas : Il ne sera plus nécessaire de demander des quotas supplémentaires lorsque vous enverrez plusieurs dizaines de milliers de messages.
  • La migration C2DM -> GCM est on ne peut plus simple : Si comme nous, vous aviez passé du temps à développer votre système, vous serez heureux de savoir que la migration ne prend que quelques minutes. Pour une fois, la documentation est très claire. GCM reste compatible dès Android 2.2 (sdk 8).
  • Des nouvelles librairies : Si vous vous souvenez, il était assez laborieux de mettre en place le système C2DM (BroadcastReceiver & co). De même côté serveur, il fallait soit-même faire une requête POST vers une url pour chaque message à envoyer. Ces temps sont révolus car Google fournit maintenant 2 librairies (gcm.jar et gcm-server.jar) ainsi que des exemples complets dans le SDK Manager d’Android.
  • La gestion du multicast : Il est désormais possible d’émettre une notification push à destination de plusieurs terminaux en une seule fois.
  • Des statistiques dans la console développeur Google Play :

GCM côté serveur

La migration de C2DM vers GCM a simplifié le développement de la partie serveur :

  • Là où il était auparavant nécessaire de poster explicitement la requête HTTP construite manuellement, une API est mise à disposition pour nous faciliter la construction et l’émission des notifications. La requête produite est à présent en JSON (par opposition à un jeu de paramètres POST).
  • Là où il était nécessaire de s’authentifier une première fois au travers de la méthode ClientLogin et d’utiliser le token généré, il suffit désormais d’utiliser la clé obtenue lors de l’enregistrement sur Google API (signup).

Le serveur assure toujours deux grandes fonctionnalités même si ces dernières ont évolué :

  • Permettre l’enregistrement des identifiants utilisateurs uniques (token) générés lors de l’abonnement aux notifications pour l’application (register).
  • Permettre la création et l’envoi de notifications PUSH aux téléphones enregistrés.

Les messages d’erreurs remontés par le service d’émission sont désormais suffisamment explicites pour permettre des mises à jour du serveur au cas où l’utilisateur aurait changé d’identifiant ou se serait désabonné. Cela limite les échanges entre les terminaux et la partie serveur.

Auparavant des communications entre le terminal et le serveur étaient nécessaires à chaque changement du token (register, update, delete) :

C2DM

Désormais davantage d’opérations sont réalisées au niveau de la partie serveur :

GCM

Le service d’enregistrement des identifiants utilisateurs ne nécessite aucune évolution. L’émissions des notifications est en revanche facilité par la mise à disposition de la librairie gcm-server (javadoc) présente dans le SDK Manager d’Android.

Google ne propose pas de repository Maven pour GCM : Pour utiliser la librairie comme artefact Maven il vous faudra l’installer manuellement ou utiliser des repos persos qui la proposent.

La création de la notification est réalisée comme suit :

// 'Builder' fournie dans la librairie gcm-server
Builder mb = new Message.Builder();

// 2 messages avec même collapseKey permettent des annuler/remplacer
mb.collapseKey(String.valueOf(System.currentTimeMillis()));

// Titre
mb.addData("title", "Octo win challenges");

// Corps du message
mb.addData("message", "Octo is the Greatest place to work 2012");

// Cle custom
mb.addData("url", "http://www.greatplacetowork.fr/meilleures-entreprises/best-workplaces-france");

// Initialisation
Message messageToSend = mb.build();

L’envoi de la notification se réalise de la manière suivante :

// ‘Sender’ fournie dans la librairie gcm-server
Sender sender = new Sender("");
List devices = new ArrayList();

// Ici remplissez avec vos Tokens
devices.add("");
devices.add("");

// Envoi
MulticastResult result = sender.sendNoRetry(messageToSend, devices);
List results = result.getResults();

// Parsing des resultats
for (int i = 0; i < devices.size(); i++) {
  Result result = results.get(i);

  if (result.getMessageId() != null) {
    logger.info(" --> Succesfully sent message to device #" + i);

    String canonicalRegId = result.getCanonicalRegistrationId();
    if (canonicalRegId != null) {
      devices.set(i, canonicalRegId);
      // < MISE A JOUR DU TOKEN EN BASE >
    }
  } else {
    String error = result.getErrorCodeName();

    if (Constants.ERROR_NOT_REGISTERED.equals(error)) {
      // < DESACTIVATION DU TOKEN EN BASE >
    } else {
      // < SYSTEME DE REJET >
    }
  }
}

La description des paramètres disponibles pour l’envoi est fournie dans la documentation Google. La gestion des résultats est décrite dans la page de migration d’une application C2DM partie serveur.

Nous avons rapproché la notion de multicast GCM avec les possibilités de Spring-Batch de travailler par lot (§5.1 Chunk Oriented Processing). Voici la cinématique ainsi réalisée :

"Spring Batch + GCM"

Nous vous proposons le code source du batch sur GitHUB

GCM côté client

Côté client, vous avez le choix :

Si vous aviez déjà développé votre module de réception de notifications à base de BroadcastReceiver, il n’est pas nécessaire de tout réécrire. Vous avez simplement à changer l’adresse email de registration par un project ID que vous récupèrerez sur la console des google APIs (dans l’url, vous avez un #project:xxxxxxxxxxxxx). Tout le code est réutilisable en l’état, y-compris les déclarations qui contiennent C2DM dans le manifest. Merci Google de ne pas avoir fait un gros trash sur le travail réalisé.

Dans le cas contraire, vous débutez directement avec GCM et dans ce cas, je vous épargne une redite du getting started guide, qui est très bien fait. Il est tout de même intéressant de voir que le travail est grandement simplifié avec la librairie gcm.jar. En effet, les BroadcastReceiver sont déjà fournis, il ne reste plus qu’à déclarer un IntentService (qui étend GCMBaseIntentService) pour implémenter les différents callbacks et donc vous concentrer sur le comportement de votre application plutôt que sur du boilerplate code.

Notifications Android 4.1

Nous allons ici présenter les nouveautés de la version 16 du SDK pour afficher une notification dans la barre de notifications (vous pouvez utiliser les notifications sans forcément faire du push). Globalement, les notifications sont plus riches : plus de texte, plus d’options, plus d’images...

BigTextStyle

Vous avez désormais la possibilité de fournir des textes de notifications plus long, évitant ainsi de tronquer le message à 20 caractères :

Builder builder16 = new Notification.Builder(context);
builder16.setStyle(new BigTextStyle().bigText(message));

BigPictureStyle

Fini les notifications tristes, vous pouvez dorénavant les agrémenter d’images :

Builder builder16 = new Notification.Builder(context);
InputStream stream = downloadImage(imageUrl);
if (stream != null) {
  Bitmap bm = BitmapFactory.decodeStream(stream);
  builder16.setStyle(new BigPictureStyle().bigPicture(bm));
}

Plusieurs points d’attention sur les images :

  • Si vous téléchargez une image depuis internet, veillez à ne pas bloquer le thread UI. La réception de la notification GCM ayant lieu dans un IntentService, il ne devrait pas y avoir de problème. Dans notre exemple, nous avons mis en place deux IntentService, un dédié aux versions antérieures au SDK 16 et l’autre pour les versions ultérieures. Cela permet de fournir des fonctionnalités avancées aux possesseurs de téléphones plus à jour sans toutefois oublier les autres.
  • Afin de s’afficher correctement, les images doivent faire moins de 450 dp de large. Après avoir testé, 400x250 semble être une bonne mesure.

InboxStyle

Vous pouvez également afficher un résumé de messages reçus. C’est très pratique pour les mails, les sms, mais on peut imaginer afficher les 3 derniers messages facebook ou bien des messages de messageries instantannées...

Builder builder16 = new Notification.Builder(context);
builder16.setStyle(new InboxStyle()
                  .addLine(message1)
                  .addLine(message2)
                  .setSummaryText("+3 more"));

Enfin, vous pouvez créer vos propres styles en étendant la classe android.app.Notification.Style. Attention, tout comme les widgets, vous ne pouvez pas utiliser tous les composants Android puisqu’il faut construire une RemoteViews qui souffre de plusieurs limitations.

Actions

Au delà des styles, il est dorénavant possible d’ajouter jusqu’à 3 boutons d’actions pour pouvoir traiter une notification sans aller jusqu’à ouvrir l’application. Exemple de bouton d’action pour partager un message, vous noterez l’utilisation des PendingIntent comme pour la notification de base :

Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.putExtra(Intent.EXTRA_TEXT, message);
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, title);

PendingIntent sharePendingIntent = PendingIntent.getActivity(context, 21245, Intent.createChooser(sharingIntent, "Share"), PendingIntent.FLAG_UPDATE_CURRENT);

Builder builder16 = new Notification.Builder(context);
builder16.addAction(android.R.drawable.ic_menu_share, "Share", sharePendingIntent);

Vous pouvez tester ces différents mécanismes avec l’application GCM Tester, dont le code source est disponible ici.

Conclusion

Si C2DM va continuer de vivre un certain temps (non mentionné par Google), il est tout de même déprécié depuis le 26 juin 2012. Nous vous conseillons donc de faire la migration vers GCM dès que possible, d'autant plus que celle-ci peut se faire en quelques minutes. Dans le cas où vous commencez une nouvelle application, la question ne se pose pas, ce sera GCM. Pour les notifications, faites plaisir aux possesseurs de téléphones/tablettes avec Android Jelly Bean, mais n'oubliez pas les utilisateurs des versions antérieures.