Gestire gli eventi del livello dati su Wear

Quando effettui una chiamata all'API Data Layer, puoi ricevere lo stato della chiamata al termine. Puoi anche ascoltare gli eventi di dati risultanti dalle modifiche ai dati apportate dalla tua app ovunque nella rete Wear OS by Google.

Per un esempio di utilizzo efficace dell'API Data Layer, consulta l'app di esempio Android DataLayer.

Attendere lo stato delle chiamate Data Layer

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

Tuttavia, in genere vuoi fare qualcosa con il risultato al termine 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 sul thread dell'interfaccia utente principale, non effettuare chiamate bloccanti all'API Data Layer e utilizza una coroutine per chiamare putDataItem:

private suspend fun Context.sendDataAsync(count: Int) {
    try {
        val putDataReq: PutDataRequest = PutDataMapRequest.create("/count").run {
            dataMap.putInt("count_key", count)
            asPutDataRequest()
        }
        val dataItem = Wearable.getDataClient(this).putDataItem(putDataReq).await()
        handleDataItem(dataItem)
    } catch (e: Exception) {
        handleDataItemError(e)
    } finally {
        handleTaskComplete()
    }
}

private fun handleDataItem(dataItem: DataItem) { }
private fun handleDataItemError(exception: Exception) { }
private fun handleTaskComplete() { }

Consulta l'API Task per altre possibilità, inclusa l'esecuzione a catena di attività diverse.

Chiamate sincrone

Se il codice è in esecuzione su un thread di gestione separato in un servizio in background, ad esempio in un WearableListenerService, utilizza runBlocking per effettuare una chiamata bloccante a putDataItem.

Nota: non chiamare questa funzione sul thread principale.

private fun Context.sendDataSync(count: Int) = runBlocking {
    val putDataReq = PutDataMapRequest.create("/count").run {
        dataMap.putInt("count_key", count)
        asPutDataRequest()
    }

    try {
        val result = Wearable.getDataClient(this@sendDataSync)
            .putDataItem(putDataReq)
            .await()
        // Logic for success
    } catch (e: Exception) {
        // Handle failure
    }
}

Ascoltare gli eventi Data Layer

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

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

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

Nota: tieni conto dell'utilizzo della batteria dell'app quando scegli un'implementazione del listener. Un WearableListenerService viene registrato nel file manifest dell'app e può avviare l'app se non è già in esecuzione. Se devi ascoltare gli eventi solo quando l'app è già in esecuzione, come spesso accade con le applicazioni interattive, non utilizzare un WearableListenerService. Registra invece un listener live. Ad esempio, utilizza il metodo addListener della DataClient classe. In questo modo puoi ridurre il carico sul sistema e l'utilizzo della batteria.

Utilizzare un WearableListenerService

In genere, crei istanze di WearableListenerService sia nelle app indossabili che in quelle portatili. Tuttavia, se non ti interessano gli eventi di dati in una delle app, non devi implementare il servizio in quell'app.

Ad esempio, puoi avere un'app portatile che imposta e recupera gli oggetti degli elementi di dati e un'app per dispositivi indossabili che rimane in ascolto di questi aggiornamenti per aggiornare la sua UI. L'app per dispositivi indossabili non aggiorna mai gli elementi di dati, quindi l'app portatile non rimane in ascolto degli eventi di dati dall'app per dispositivi indossabili.

Di seguito sono riportati alcuni degli eventi che puoi ascoltare utilizzando WearableListenerService:

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

Puoi anche ascoltare gli eventi di ChannelClient.ChannelCallback, ad esempio onChannelOpened().

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

Per creare un WearableListenerService:

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

L'esempio seguente mostra come implementare un WearableListenerService:

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

La sezione seguente 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=".snippets.datalayer.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>

Il filtro di azioni DATA_CHANGED indica al sistema che la tua app è interessata agli eventi del livello dati.

In questo esempio, lo smartwatch ascolta l'elemento di dati /start-activity e lo smartphone ascolta la risposta del messaggio /data-item-received (DATA_ITEM_RECEIVED_PATH).

Vengono applicate le regole di corrispondenza dei filtri Android standard. Puoi specificare più servizi per file manifest, più filtri di intent per servizio, più azioni per filtro e più stanze di dati per filtro. I filtri possono corrispondere a un host con caratteri jolly o a uno specifico. Per la corrispondenza con un host con caratteri jolly, utilizza host="*". Per la corrispondenza con un host specifico, specifica host=<node_id>.

Puoi anche trovare la corrispondenza con un percorso letterale o un prefisso del percorso. Per farlo, devi specificare un host con caratteri jolly o 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 dell'API per WearableListenerService.

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

Quando trovi la corrispondenza con i filtri di intent, ricorda due regole importanti:

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

Utilizzare un listener live

Se la tua app si occupa solo degli eventi del livello dati quando l'utente interagisce con l'app, potrebbe non essere necessario un servizio a lunga esecuzione per gestire ogni modifica dei dati. In questo caso, puoi ascoltare gli eventi in un'attività.

Per un approccio più pulito e sicuro, utilizza un osservatore del ciclo di vita. Utilizzando un osservatore del ciclo di vita, sposti la logica di registrazione da LifecycleResumeEvent dell'attività a una classe separata e riutilizzabile che implementa DefaultLifecycleObserver.

Questo approccio mantiene l'attività snella e previene bug comuni come la dimenticanza di annullare la registrazione del listener.

1. Creare il listener che riconosce il ciclo di vita

Questa classe esegue il wrapping di DataClient.OnDataChangedListener e gestisce automaticamente il proprio abbonamento in base al ciclo di vita dell'attività.

class WearDataLayerObserver(
    private val dataClient: DataClient,
    private val onDataReceived: (DataEventBuffer) -> Unit
) : DefaultLifecycleObserver, DataClient.OnDataChangedListener {

    // Implementation of the DataClient listener
    override fun onDataChanged(dataEvents: DataEventBuffer) {
        onDataReceived(dataEvents)
    }

    // Automatically register when the Activity starts
    override fun onResume(owner: LifecycleOwner) {
        dataClient.addListener(this)
    }

    // Automatically unregister when the Activity pauses
    override fun onPause(owner: LifecycleOwner) {
        dataClient.removeListener(this)
    }
}

2. Utilizzo nell'attività

Ora, la tua attività non deve utilizzare LifecycleResumeEvent o onPause per l'API Wear. Registra l'osservatore una volta in LaunchedEvent (o onCreate).

class DataLayerLifecycleActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val dataClient = Wearable.getDataClient(this)

        // Create the observer and link it to the activity's lifecycle
        val wearObserver = WearDataLayerObserver(dataClient) { dataEvents ->
            handleDataEvents(dataEvents)
        }

        lifecycle.addObserver(wearObserver)
    }

    private fun handleDataEvents(dataEvents: DataEventBuffer) {
        // ... filter and process events ...
    }
}

Perché è meglio:

  • Attività più pulita: rimuovi il codice boilerplate dai metodi del ciclo di vita dell'attività.
  • Sicurezza: DefaultLifecycleObserver aiuta a verificare che il listener venga rimosso anche se l'attività viene eliminata in modo imprevisto, evitando perdite di memoria.
  • Riutilizzabilità: puoi collegare questo WearDataLayerObserver a qualsiasi attività o componibile senza riscrivere la logica di registrazione.
  • Disaccoppiamento: la logica per l'ascolto è separata dalla logica di cosa fare con i dati.

Utilizzare i filtri con i listener live

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

Un pattern comune consiste nel registrare un listener con un percorso o un prefisso del percorso specifico utilizzando collectAsStateWithLifecycle(). Implementando i listener in questo modo, la tua app può ricevere gli eventi in modo più selettivo, migliorandone la progettazione e l'efficienza.