Gérer les événements Data Layer sur Wear

Lorsque vous appelez l'API Data Layer, vous pouvez recevoir l'état de l'appel lorsqu'il est terminé. Vous pouvez également écouter les événements de données résultant des modifications de données apportées par votre application n'importe où sur le réseau Wear OS by Google.

Pour voir un bon exemple d'utilisation de l'API Data Layer, consultez l'exemple d'application Android DataLayer.

Attendre l'état des appels Data Layer

Les appels de l'API Data Layer (par exemple un appel utilisant la méthode putDataItem de la classe DataClient) renvoient parfois un objet Task<ResultType>. Dès que l'objet Task est créé, l'opération est mise en file d'attente en arrière-plan. Si vous ne faites rien d'autre, l'opération s'achèvera silencieusement.

Toutefois, il faudra généralement effectuer une action sur le résultat une fois l'opération terminée. L'objet Task vous permet donc d'attendre l'état du résultat, de manière asynchrone ou synchrone.

Appels asynchrones

Si votre code est exécuté sur le thread UI principal, n'effectuez pas d'appels bloquants à l'API Data Layer. Exécutez les appels de manière asynchrone en ajoutant une méthode de rappel à l'objet Task, qui se déclenchera une fois l'opération terminée :

Kotlin

// Using Kotlin function references
task.addOnSuccessListener(::handleDataItem)
task.addOnFailureListener(::handleDataItemError)
task.addOnCompleteListener(::handleTaskComplete)
...
fun handleDataItem(dataItem: DataItem) { ... }
fun handleDataItemError(exception: Exception) { ... }
fun handleTaskComplete(task: Task<DataItem>) { ... }

Java

// Using Java 8 Lambdas.
task.addOnSuccessListener(dataItem -> handleDataItem(dataItem));
task.addOnFailureListener(exception -> handleDataItemError(exception));
task.addOnCompleteListener(task -> handleTaskComplete(task));

Consultez l'API Tasks pour découvrir d'autres options, y compris la possibilité d'enchaîner l'exécution de différentes tâches.

Appels synchrones

Si votre code s'exécute sur un thread de gestionnaire distinct d'un service d'arrière-plan (comme dans un WearableListenerService), les appels peuvent être bloqués sans que cela ne pose problème. Dans ce cas, vous pouvez appeler Tasks.await() au niveau de l'objet Task, qui se bloque jusqu'à la fin de la requête et renvoie un objet Result : Ce processus est illustré dans l'exemple suivant.

Remarque : Veillez à ne pas appeler cette méthode sur le thread principal.

Kotlin

try {
    Tasks.await(dataItemTask).apply {
        Log.d(TAG, "Data item set: $uri")
    }
}
catch (e: ExecutionException) { ... }
catch (e: InterruptedException) { ... }

Java

try {
    DataItem item = Tasks.await(dataItemTask);
    Log.d(TAG, "Data item set: " + item.getUri());
} catch (ExecutionException | InterruptedException e) {
  ...
}

Écouter les événements Data Layer

Étant donné que la couche de données synchronise et envoie des données sur l'appareil portable et connecté, il est généralement nécessaire d'écouter les événements importants tels que les créations d'éléments de données et la réception de messages.

Pour écouter les événements de la couche de données, vous avez deux options :

Avec ces deux options, vous remplacez les méthodes de rappel pour les événements de données que vous souhaitez gérer.

Remarque : Tenez compte de l'utilisation de la batterie par votre application lorsque vous choisissez une implémentation d'écouteur. Un WearableListenerService est enregistré dans le fichier manifeste de l'application et peut lancer l'application si elle n'est pas déjà en cours d'exécution. Si vous n'avez besoin d'écouter des événements que lorsque votre application est déjà en cours d'exécution (ce qui est souvent le cas avec les applications interactives), n'utilisez pas de WearableListenerService. Enregistrez plutôt un écouteur en direct. Par exemple, utilisez la méthode addListener de la classe DataClient. Cela peut alléger la charge du système et l'utilisation de la batterie.

Utiliser un WearableListenerService

Vous créerez généralement des instances de WearableListenerService dans des applications pour appareils connectés ou portables. Mais si les événements de données ne vous intéressent pas dans l'une de ces applications, il n'est pas indispensable d'y implémenter ce service.

Par exemple, vous pouvez avoir une application portable qui définit et obtient des objets d'éléments de données, et une application connectée qui écoute ces modifications afin d'ajuster son UI en conséquence. L'application connectée ne met jamais à jour aucun des éléments de données. Par conséquent, l'application portable n'écoute aucun événement de données de l'application connectée.

Voici certains des événements que vous pouvez écouter à l'aide d'un WearableListenerService :

  • onDataChanged() : lorsqu'un objet d'élément de données est créé, supprimé ou modifié, le système déclenche ce rappel sur tous les nœuds connectés.
  • onMessageReceived() : un message envoyé par un nœud déclenche ce rappel sur le nœud cible.
  • onCapabilityChanged() : lorsqu'une fonctionnalité annoncée par une instance de votre application devient disponible sur le réseau, cet événement déclenche ce rappel. Si vous recherchez un nœud à proximité, vous pouvez interroger la méthode isNearby() pour les nœuds fournis dans le rappel.

Vous pouvez également écouter des événements de ChannelClient.ChannelCallback, tels que onChannelOpened().

Tous les événements précédents sont exécutés dans un thread d'arrière-plan et non sur le thread principal.

Pour créer un WearableListenerService, procédez comme suit :

  1. Créez une classe qui étend WearableListenerService.
  2. Écoutez les événements qui vous intéressent, comme onDataChanged().
  3. Déclarez un filtre d'intent dans le fichier manifeste Android pour informer le système de votre WearableListenerService. Cette déclaration permet au système d'associer votre service si nécessaire.

L'exemple suivant montre comment implémenter un WearableListenerService simple :

Kotlin

private const val TAG = "DataLayerSample"
private const val START_ACTIVITY_PATH = "/start-activity"
private const val DATA_ITEM_RECEIVED_PATH = "/data-item-received"

class DataLayerListenerService : WearableListenerService() {

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onDataChanged: $dataEvents")
        }

        // Loop through the events and send a message
        // to the node that created the data item.
        dataEvents.map { it.dataItem.uri }
                .forEach { uri ->
                    // Get the node ID from the host value of the URI.
                    val nodeId: String = uri.host
                    // Set the data of the message to be the bytes of the URI.
                    val payload: ByteArray = uri.toString().toByteArray()

                    // Send the RPC.
                    Wearable.getMessageClient(this)
                            .sendMessage(nodeId, DATA_ITEM_RECEIVED_PATH, payload)
                }
    }
}

Java

public class DataLayerListenerService extends WearableListenerService {
    private static final String TAG = "DataLayerSample";
    private static final String START_ACTIVITY_PATH = "/start-activity";
    private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onDataChanged: " + dataEvents);
        }

        // Loop through the events and send a message
        // to the node that created the data item.
        for (DataEvent event : dataEvents) {
            Uri uri = event.getDataItem().getUri();

            // Get the node ID from the host value of the URI.
            String nodeId = uri.getHost();
            // Set the data of the message to be the bytes of the URI.
            byte[] payload = uri.toString().getBytes();

            // Send the RPC.
            Wearable.getMessageClient(this).sendMessage(
                  nodeId,  DATA_ITEM_RECEIVED_PATH, payload);
        }
    }
}

La section suivante explique comment utiliser un filtre d'intent avec cet écouteur.

Utiliser des filtres avec un WearableListenerService

Un filtre d'intent pour l'exemple WearableListenerService de la section précédente peut se présenter comme suit :

<service android:name=".DataLayerListenerService" android:exported="true" tools:ignore="ExportedService" >
  <intent-filter>
      <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
      <data android:scheme="wear" android:host="*"
               android:path="/start-activity" />
  </intent-filter>
</service>

Dans ce filtre, l'action DATA_CHANGED remplace l'action BIND_LISTENER précédemment recommandée afin que seuls des événements spécifiques déclenchent ou lancent votre application. Cette modification améliore l'efficacité du système et réduit, entre autres, la consommation de la batterie associée à votre application. Dans cet exemple, la montre écoute l'élément de données /start-activity, et le téléphone écoute la réponse au message /data-item-received.

Les règles standards de correspondance des filtres Android s'appliquent. Vous pouvez spécifier plusieurs services par fichier manifeste, plusieurs filtres d'intent par service, plusieurs actions par filtre et plusieurs strophes de données par filtre. Les filtres peuvent correspondre à un hôte générique ou à un hôte spécifique. Pour établir une correspondance sur un hôte générique, utilisez host="*". Pour établir une correspondance sur un hôte spécifique, spécifiez host=<node_id>.

Vous pouvez également établir une correspondance avec un chemin d'accès littéral ou un préfixe de chemin d'accès. Pour ce faire, vous devez spécifier un caractère générique ou un hôte spécifique. Dans le cas contraire, le chemin d'accès que vous spécifiez sera ignoré par le système.

Pour en savoir plus sur les types de filtres compatibles avec Wear OS, consultez la documentation de référence de l'API pour WearableListenerService.

Pour en savoir plus sur les filtres de données et les règles de correspondance, consultez la documentation de référence de l'API pour l'élément manifeste <data>.

Lorsque vous faites correspondre des filtres d'intents, tenez compte de deux règles importantes :

  • Si aucun schéma n'est spécifié pour le filtre d'intent, le système ignore tous les autres attributs d'URI.
  • Si aucun hôte n'est spécifié pour le filtre, le système ignore tous les attributs de chemin.

Utiliser un écouteur en direct

Si votre application ne s'intéresse qu'aux événements de la couche de données lorsque l'utilisateur interagit avec elle, il se peut qu'elle n'ait pas besoin d'un service de longue durée pour gérer chaque modification des données. Dans ce cas, vous pouvez écouter les événements d'une activité en implémentant une ou plusieurs des interfaces suivantes :

Pour créer une activité qui écoute des événements de données, procédez comme suit :

  1. Implémentez les interfaces souhaitées.
  2. Dans la méthode onCreate() ou onResume(), appelez Wearable.getDataClient(this).addListener(), MessageClient.addListener(), CapabilityClient.addListener() ou ChannelClient.registerChannelCallback() pour avertir les services Google Play que votre activité souhaite écouter pour les événements de couche de données.
  3. Dans onStop() ou onPause(), annulez l'enregistrement des écouteurs avec DataClient.removeListener(), MessageClient.removeListener(), CapabilityClient.removeListener() ou ChannelClient.unregisterChannelCallback().
  4. Si une activité ne s'intéresse qu'aux événements ayant un préfixe de chemin spécifique, vous pouvez ajouter un écouteur avec un filtre de préfixe approprié afin de ne recevoir que les données pertinentes pour l'état actuel de l'application.
  5. Implémentez onDataChanged(), onMessageReceived(), onCapabilityChanged() ou les méthodes de ChannelClient.ChannelCallback, en fonction des interfaces que vous avez implémentées. Ces méthodes sont appelées sur le thread principal. Vous pouvez également spécifier un Looper personnalisé avec WearableOptions.

Voici un exemple de mise en œuvre de DataClient.OnDataChangedListener :

Kotlin

class MainActivity : Activity(), DataClient.OnDataChangedListener {

    public override fun onResume() {
        Wearable.getDataClient(this).addListener(this)
    }

    override fun onPause() {
        Wearable.getDataClient(this).removeListener(this)
    }

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        dataEvents.forEach { event ->
            if (event.type == DataEvent.TYPE_DELETED) {
                Log.d(TAG, "DataItem deleted: " + event.dataItem.uri)
            } else if (event.type == DataEvent.TYPE_CHANGED) {
                Log.d(TAG, "DataItem changed: " + event.dataItem.uri)
            }
        }
    }
}

Java

public class MainActivity extends Activity implements DataClient.OnDataChangedListener {

    @Override
    public void onResume() {
        Wearable.getDataClient(this).addListener(this);
    }

    @Override
    protected void onPause() {
        Wearable.getDataClient(this).removeListener(this);
    }

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        for (DataEvent event : dataEvents) {
            if (event.getType() == DataEvent.TYPE_DELETED) {
                Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
            } else if (event.getType() == DataEvent.TYPE_CHANGED) {
                Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
            }
        }
    }
}

Utiliser des filtres avec des écouteurs en direct

Comme indiqué précédemment, tout comme vous pouvez spécifier des filtres d'intent pour les objets WearableListenerService basés sur le fichier manifeste, vous pouvez en utiliser lorsque vous enregistrez un écouteur via l'API Wearable. Les mêmes règles s'appliquent aux écouteurs en direct basés sur l'API et aux écouteurs basés sur le fichier manifeste.

Un modèle courant consiste à enregistrer un écouteur avec un chemin d'accès ou un préfixe de chemin spécifique dans la méthode onResume() d'une activité, puis à le supprimer dans la méthode onPause() de la même activité. Implémenter des écouteurs de cette manière permet à votre application de recevoir des événements de façon plus sélective, ce qui améliore sa conception et son efficacité.