Gerenciar eventos de Data Layer no Wear

Ao chamar a API Data Layer, você pode receber o status da chamada quando ela for concluída. Você também pode detectar eventos de dados resultantes das alterações que o app faz em qualquer local da rede do Wear OS by Google.

Observação: um app para Wear pode se comunicar com um app para smartphone usando a API Data Layer, mas não é recomendado se conectar a uma rede usando essa API.

Confira os seguintes recursos relacionados:

Aguardar o status das chamadas da Data Layer

As chamadas para a API Data Layer, por exemplo, com o método putDataItem da classe DataClient, podem retornar um objeto Task<ResultType>. Assim que o objeto Task é criado, a operação é colocada na fila em segundo plano. Se você não fizer nada depois disso, a operação será concluída silenciosamente. No entanto, você provavelmente fará algo com o resultado após a conclusão da operação, então o objeto Task permite que você aguarde o status do resultado de forma síncrona ou assíncrona.

Chamadas assíncronas

Se o código estiver sendo executado na linha de execução de IU principal, não faça chamadas de bloqueio para a API Data Layer. Você pode executar as chamadas de forma assíncrona adicionando um método de callback ao objeto Task, que será acionado quando a operação for concluída:

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

Veja a API Task para outras possibilidades, incluindo a capacidade de encadear a execução de tarefas diferentes simultaneamente.

Chamadas síncronas

Se o código está sendo executado em outra linha de execução de gerenciador em um serviço em segundo plano (que é o caso em um WearableListenerService), não há problema em fazer chamadas de bloqueio. Nesse caso, você pode chamar Tasks.await() no objeto Task, que fica bloqueado até que a solicitação seja concluída e retorne um objeto Result:

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

Detectar eventos de Data Layer

Como a camada de dados sincroniza e envia dados entre os dispositivos portátil e wearable, geralmente é necessário detectar eventos importantes. Exemplos desse tipo de evento incluem a criação de itens de dados e o recebimento de mensagens.

Para detectar eventos de Data Layer, você tem duas opções:

Com as duas opções, você pode modificar os métodos de callback de evento de dados para os eventos que você está interessado em gerenciar.

Observação: quanto ao uso da bateria, um WearableListenerService é registrado no manifesto do app e poderá iniciar o app se ele ainda não estiver em execução. Se você só precisa detectar eventos quando o app já está em execução, o que geralmente é o caso para apps interativos, não use um WearableListenerService. Registre um listener ativo usando, por exemplo, o método addListener da classe DataClient. Isso pode reduzir a carga no sistema e reduzir o uso da bateria.

Com um WearableListenerService

Normalmente, você pode criar instâncias desse serviço tanto no app wearable como no portátil. Se você não tiver interesse em eventos de dados em um desses apps, não é necessário implementar o serviço nesse app específico.

Por exemplo, você pode ter um app portátil que configura e recebe objetos de itens de dados e um app wearable que detecta essas alterações para atualizar a IU. O wearable nunca atualiza nenhum item de dados, então o app do dispositivo portátil não detecta eventos de dados do app wearable.

Alguns dos eventos que você pode detectar usando WearableListenerService são:

  • onDataChanged(): sempre que um objeto de item de dados é criado, excluído ou mudado, o sistema aciona esse callback em todos os nós conectados.
  • onMessageReceived(): uma mensagem enviada de um nó aciona esse callback no nó de destino.
  • onCapabilityChanged(): quando um recurso divulgado por uma instância do seu app se torna disponível na rede, o evento aciona esse callback. Se você estiver procurando um nó próximo, é possível consultar o método isNearby() dos nós fornecidos no callback.

Além dos que estão na lista, é possível detectar eventos do ChannelClient.ChannelCallback, como onChannelOpened().

Todos os eventos acima são executados em uma linha de execução em segundo plano, não na principal.

Para criar um WearableListenerService, siga estas etapas:

  1. Crie uma classe que estenda WearableListenerService.
  2. Detecte eventos em que você tenha interesse, como onDataChanged().
  3. Declare um filtro de intent no seu manifesto do Android para notificar o sistema sobre seu WearableListenerService. Essa declaração permite que o sistema vincule seu serviço conforme necessário.

O exemplo a seguir mostra como implementar um WearableListenerService simples:

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

A próxima seção explica como usar um filtro de intent com esse listener.

Usar filtros com o WearableListenerService

O filtro de intent para o exemplo de WearableListenerService mostrado na seção anterior pode ter a seguinte aparência:

    <service android:name=".DataLayerListenerService">
      <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>
    

Nesse filtro, a ação DATA_CHANGED substitui a BIND_LISTENER previamente recomendada para que apenas eventos específicos ativem ou iniciem seu app. Essa mudança melhora a eficiência do sistema e reduz o consumo da bateria e outras sobrecargas associadas ao app. Nesse exemplo, o relógio detecta o item de dados /start-activity, e o smartphone detecta a resposta de mensagem /data-item-received.

As regras padrão de correspondência de filtros Android são aplicáveis. Você pode especificar mais de um serviço por manifest, mais de um filtro de intent por serviço, mais de uma ação por filtro e mais de uma estrofe de dados por filtro. Os filtros podem corresponder a um host curinga ou a um específico. Para fazer correspondência com um host curinga, use host="*". Para fazer correspondência com um host específico, especifique host=<node_id>.

Também é possível fazer correspondência com um caminho literal ou prefixo de caminho. Se você estiver fazendo correspondência por caminho ou prefixo de caminho, é necessário definir um host curinga ou específico. Caso não queira fazer isso, o sistema ignorará o caminho que você especificou.

Para saber mais sobre os tipos de filtro compatíveis com o Wear, consulte a documentação de referência da API para WearableListenerService.

Para saber mais sobre filtros de dados e regras de correspondência, consulte a documentação de referência da API para o elemento de manifesto data.

Há duas regras importantes para fazer a correspondência de filtros de intent:

  • Se não houver um esquema especificado para o filtro de intent, o sistema ignorará todos os outros atributos de URI.
  • Se não houver um host especificado para o filtro, o sistema ignorará todos os atributos do caminho.

Com um listener em tempo real

Se seu app se importar com eventos de camada de dados somente quando o usuário estiver interagindo com ele, pode não ser necessário ter um serviço de execução longa para processar cada alteração de dados. Nesse caso, você pode detectar eventos em uma atividade implementando uma ou mais das seguintes interfaces:

Para criar uma atividade que detecta eventos de dados:

  1. Implemente as interfaces desejadas.
  2. No método onCreate() ou onResume(), chame Wearable.getDataClient(this).addListener(), MessageClient.addListener(), CapabilityClient.addListener() ou ChannelClient.registerChannelCallback() para notificar o Google Play Services de que sua atividade está interessada em detectar eventos da Data Layer.
  3. Em onStop() ou onPause(), cancele o registro de listeners com DataClient.removeListener(), MessageClient.removeListener(), CapabilityClient.removeListener() ou ChannelClient.unregisterChannelCallback().
  4. Se um atividade só tiver interesse em eventos com um prefixo de caminho específico, você pode adicionar um listener com um filtro de prefixos adequados para só receber dados relevantes ao status atual do aplicativo.
  5. Implemente onDataChanged(), onMessageReceived(), onCapabilityChanged() ou métodos de ChannelClient.ChannelCallback, dependendo das interfaces que você implementou. Esses métodos são chamados na linha de execução principal, ou você pode especificar um Looper personalizado usando WearableOptions.

Veja um exemplo que 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());
                }
            }
        }
    }
    

Usar filtros com listeners em tempo real

Conforme observado anteriormente nesta página, assim como é possível especificar filtros de intent para objetos WearableListenerService do manifesto, também é possível usar filtros de intent ao registrar um listener em tempo real por meio da API Wearable. As mesmas regras se aplicam aos listeners em tempo real da API e aos listeners do manifesto.

Um padrão comum é registrar um listener com um caminho específico ou prefixo de caminho no método onResume() de uma atividade e remover o listener no método onPause() da atividade. A implementação de listeners dessa maneira permite que o app receba eventos de forma mais seletiva, melhorando o design e a eficiência.