Gestire gli eventi del livello dati su Wear

Quando effettui una chiamata all'API del livello dati, puoi ricevere lo stato della chiamata al completamento. Puoi anche rimanere in ascolto degli eventi di dati derivanti dalle modifiche ai dati apportate dalla tua app ovunque sulla rete Wear OS by Google.

Per un esempio di utilizzo efficace dell'API del livello dati, consulta l' app Android Datalayer Sample.

Attendi lo stato delle chiamate del livello dati

Le chiamate all'API del livello dati, ad esempio una chiamata che utilizza il metodo putDataItem della classe DataClient, a volte restituiscono un oggetto Task<ResultType>. Non appena viene creato l'oggetto Task, l'operazione viene messa in coda in background. Se dopo questa operazione non fai altro, l'operazione viene completata in modo invisibile all'utente.

Tuttavia, di solito vuoi eseguire un'azione con il risultato dopo il completamento dell'operazione, quindi l'oggetto Task ti consente di attendere lo stato del risultato, in modo asincrono o sincrono.

Chiamate asincrone

Se il codice è in esecuzione nel thread dell'interfaccia utente principale, non effettuare chiamate di blocco all'API del livello dati. Esegui le chiamate in modo asincrono aggiungendo un metodo di callback all'oggetto Task, che si attiva quando l'operazione viene completata:

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

Consulta l' API Tasks per altre possibilità, tra cui il concatenamento dell'esecuzione di diverse attività.

Chiamate sincrone

Se il tuo codice è in esecuzione su un thread di gestore separato in un servizio in background, ad esempio in un WearableListenerService, le chiamate possono essere bloccate. In questo caso, puoi chiamare Tasks.await() sull'oggetto Task, che blocca fino al completamento della richiesta e restituisce un oggetto Result. Questa situazione è illustrata nell'esempio seguente.

Nota:assicurati di non chiamare questa opzione mentre ti trovi nel thread principale.

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) {
  ...
}

Ascolta gli eventi del livello dati

Poiché il livello dati sincronizza e invia dati tra i dispositivi portatili e indossabili, in genere devi rimanere in ascolto di eventi importanti come la creazione degli elementi di dati e la ricezione dei messaggi.

Per ascoltare gli eventi del livello dati, hai due opzioni:

Con entrambe le opzioni, esegui l'override dei metodi di callback degli eventi dei dati per gli eventi che ti interessa gestire.

Nota: quando scegli l'implementazione di un listener, considera l'utilizzo della batteria da parte dell'app. Un WearableListenerService viene registrato nel file manifest dell'app e può avviarla se non è già in esecuzione. Se devi ascoltare gli eventi solo quando la tua app è già in esecuzione, come spesso avviene nel caso delle applicazioni interattive, non utilizzare WearableListenerService. Registra invece un listener dal vivo. Ad esempio, utilizza il metodo addListener della classe DataClient. Ciò può ridurre il carico sul sistema e l'utilizzo della batteria.

Usa WearableListenerService

In genere, vengono create istanze di WearableListenerService sia nelle app per dispositivi indossabili sia nelle app per dispositivi portatili. Tuttavia, se non ti interessano gli eventi di dati in una delle app, non è necessario implementare il servizio in quell'app.

Ad esempio, puoi avere un'app portatile che imposta e recupera oggetti di elementi di dati e un'app indossabile che ascolta questi aggiornamenti per aggiornare la sua UI. L'app indossabile non aggiorna mai alcun elemento di dati, quindi l'app portatile non rileva gli eventi di dati provenienti dall'app indossabile.

Ecco alcuni degli eventi che puoi ascoltare utilizzando WearableListenerService:

  • onDataChanged(): ogni volta che un oggetto elemento di dati viene creato, eliminato o modificato, il sistema attiva questo callback su tutti i nodi connessi.
  • onMessageReceived(): un messaggio inviato da un nodo attiva questo callback sul nodo di destinazione.
  • onCapabilityChanged(): quando una funzionalità pubblicizzata da un'istanza della tua app diventa disponibile sulla rete, l'evento attiva questo callback. Se stai cercando un nodo nelle vicinanze, puoi eseguire una query sul metodo isNearby() dei nodi forniti nel callback.

Puoi anche rimanere in ascolto degli eventi di ChannelClient.ChannelCallback, ad esempio onChannelOpened().

Tutti gli eventi precedenti vengono eseguiti in un thread in background, non nel thread principale.

Per creare un WearableListenerService, segui questi passaggi:

  1. Crea un corso che estenda WearableListenerService.
  2. Ascolta gli eventi che ti interessano, ad esempio onDataChanged().
  3. Dichiara un filtro per intent nel file manifest Android per inviare una notifica al sistema in merito a WearableListenerService. Questa dichiarazione consente al sistema di associare il tuo servizio in base alle esigenze.

L'esempio seguente mostra come implementare un WearableListenerService semplice:

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 seguente sezione spiega come utilizzare un filtro per intent con questo listener.

Utilizzare i filtri con WearableListenerService

Un filtro per intent per l'esempio WearableListenerService mostrato nella sezione precedente potrebbe avere il seguente aspetto:

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

In questo filtro, l'azione DATA_CHANGED sostituisce l'azione BIND_LISTENER consigliata in precedenza in modo che solo eventi specifici attivino o avviino l'app. Questa modifica migliora l'efficienza del sistema e riduce il consumo di batteria e altri costi aggiuntivi associati alla tua app. In questo esempio, l'orologio ascolta l'elemento di dati /start-activity e il telefono ascolta la risposta al messaggio /data-item-received.

Si applicano le regole di corrispondenza standard del filtro Android. Puoi specificare più servizi per manifest, più filtri di intent per servizio, più azioni per filtro e più stanze di dati per filtro. I filtri possono corrispondere su un host con caratteri jolly o su uno specifico. Per trovare una corrispondenza su un host con caratteri jolly, utilizza host="*". Per trovare corrispondenze su un host specifico, specifica host=<node_id>.

Puoi anche creare una corrispondenza con un percorso letterale o un prefisso di percorso. Per farlo, devi specificare un carattere jolly o un host specifico. In caso contrario, il sistema ignora il percorso specificato.

Per ulteriori informazioni sui tipi di filtri supportati da Wear OS, consulta la documentazione di riferimento API per WearableListenerService.

Per ulteriori informazioni sui filtri dei dati e sulle regole di corrispondenza, consulta la documentazione di riferimento API per l'elemento manifest <data>.

Quando fai corrispondere i filtri per intent, tieni a mente due regole importanti:

  • Se non viene specificato nessuno schema per il filtro per intent, il sistema ignora tutti gli altri attributi URI.
  • Se non è specificato alcun host per il filtro, il sistema ignora tutti gli attributi del percorso.

Usare un listener dal vivo

Se alla tua app interessano gli eventi a livello dati solo quando l'utente interagisce con l'app, potrebbe non aver bisogno di un servizio a lunga esecuzione per gestire ogni modifica ai dati. In tal caso, puoi rimanere in ascolto degli eventi in un'attività implementando una o più delle seguenti interfacce:

Per creare un'attività che ascolti gli eventi di dati:

  1. Implementa le interfacce desiderate.
  2. Nel metodo onCreate() o onResume(), chiama Wearable.getDataClient(this).addListener(), MessageClient.addListener(), CapabilityClient.addListener() o ChannelClient.registerChannelCallback() per comunicare a Google Play Services che la tua attività è interessata ad ascoltare eventi del livello dati.
  3. In onStop() o onPause(), annulla la registrazione di tutti gli ascoltatori con DataClient.removeListener(), MessageClient.removeListener(), CapabilityClient.removeListener() o ChannelClient.unregisterChannelCallback().
  4. Se un'attività è interessata solo agli eventi con un prefisso percorso specifico, puoi aggiungere un listener con un filtro prefisso adatto per ricevere solo i dati pertinenti per lo stato attuale dell'applicazione.
  5. Implementa onDataChanged(), onMessageReceived(), onCapabilityChanged() o i metodi di ChannelClient.ChannelCallback, a seconda delle interfacce che hai implementato. Questi metodi vengono chiamati nel thread principale. In alternativa, puoi specificare un Looper personalizzato utilizzando WearableOptions.

Ecco un esempio che implementa 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());
            }
        }
    }
}

Utilizzare i filtri con i listener dal vivo

Come accennato in precedenza, così come puoi specificare i filtri per intent per gli oggetti WearableListenerService basati su manifest, puoi utilizzare i filtri per intent quando registri un listener in tempo reale tramite l'API Wearable. Le stesse regole si applicano sia ai listener live basati su API sia ai listener basati su manifest.

Un pattern comune è la registrazione di un listener con un percorso o un prefisso di percorso specifico nel metodo onResume() di un'attività per poi rimuovere il listener nel metodo onPause() dell'attività. L'implementazione dei listener in questo modo consente alla tua app di ricevere eventi in modo più selettivo, migliorandone il design e l'efficienza.