Gerenciar eventos de camada de dados no Wear

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Ao chamar a API Data Layer, você pode receber o status da chamada quando ela for concluída. Também é possível detectar eventos de dados resultantes de mudanças feitas pelo app em qualquer local da rede do Wear OS by Google.

Confira os recursos relacionados abaixo:

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

Para ver outras possibilidades, consulte a API Task, que inclui a opção 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, chame 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 o dispositivo portátil e o wearable, geralmente é necessário detectar eventos importantes. Alguns 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 ambas as opções, é possível modificar os métodos de callback de evento de dados para os eventos que você pretende gerenciar.

Observação: quanto ao uso da bateria, um WearableListenerService é registrado no manifesto do app e pode 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 o método addListener da classe DataClient, por exemplo. Isso pode reduzir a carga no sistema e, assim, reduzir o uso da bateria.

Com um WearableListenerService

Geralmente, é possível criar instâncias desse serviço tanto no app para dispositivos portáteis quanto no app para wearables. 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 para dispositivos portáteis que configura e recebe objetos de itens de dados e um app para wearables que detecte essas mudanças para atualizar a IU. O wearable não atualiza nenhum item de dados, então o app para dispositivos portáteis não detecta eventos de dados do app para wearables.

Alguns dos eventos que podem ser detectados usando WearableListenerService são:

  • onDataChanged(): sempre que um objeto de item de dados é criado, excluído ou modificado, 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. Caso precise de um nó próximo, você pode 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 o WearableListenerService.
  2. Detecte eventos em que você tenha interesse, como onDataChanged().
  3. Declare um filtro de intent no 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

Um filtro de intent para o exemplo de WearableListenerService apresentado 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 o 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 smartwatch 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. É possível especificar vários serviços por manifesto, vários filtros de intent por serviço, várias ações por filtro e várias estrofes de dados por filtro. Os filtros podem corresponder a um host curinga ou 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 vai ignorar o caminho que você especificou.

Para saber mais sobre os tipos de filtro com suporte no Wear OS, 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 vai ignorar todos os outros atributos de URI.
  • Se nenhum host for especificado para o filtro, todos os atributos do caminho serão ignorados pelo sistema.

Com um listener em tempo real

Caso o app se importe 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, é possível 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 informar ao Google Play Services que a atividade precisa detectar eventos da camada de dados.
  3. Em onStop() ou onPause(), cancele o registro de listeners com DataClient.removeListener(), MessageClient.removeListener(), CapabilityClient.removeListener() ou ChannelClient.unregisterChannelCallback().
  4. Caso uma atividade só precise detectar eventos com um prefixo de caminho específico, adicione um listener com um filtro de prefixos para receber apenas dados relevantes ao status 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. Também é possível 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 usando a 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 do 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.