Gerenciar eventos de camada de dados no Wear

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

Para um exemplo de como trabalhar com a API Data Layer de maneira eficaz, confira o app de exemplo Android DataLayer (link em inglês).

Aguardar o status das chamadas de Data Layer

As chamadas para a API Data Layer, por exemplo, usando 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, é mais provável que você faça algo com o resultado quando a operação for concluída. Por isso, o objeto Task permite aguardar o status do resultado de forma assíncrona ou síncrona.

Chamadas assíncronas

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

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

Consulte a API Task para descobrir outras possibilidades, incluindo a opção de encadear a execução de tarefas diferentes.

Chamadas síncronas

Se o código está sendo executado em outra linha de execução do gerenciador em um serviço em segundo plano, como no caso de 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. Esta chamada é mostrada no exemplo abaixo.

Observação: não faça essa chamada na linha de execução 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) {
  ...
}

Ouvir eventos da Data Layer

Como a camada de dados sincroniza e envia dados entre o dispositivo portátil e o wearable, geralmente é necessário ouvir eventos importantes, como a criação de itens de dados e o recebimento de mensagens.

Para ouvir eventos da Data Layer, você tem duas opções:

Com ambas as opções, é possível substituir os métodos de callback de evento de dados para os eventos que você pretende gerenciar.

Observação: considere o consumo de bateria do app ao escolher qual listener será implementado. Um WearableListenerService é registrado no arquivo de manifesto e pode iniciar o app se ele ainda não estiver em execução. Se você só precisa ouvir eventos quando o app já está em execução (o que geralmente é o caso para apps interativos), não use um WearableListenerService. Em vez disso, registre um listener em tempo real. Por exemplo, use o método addListener da classe DataClient. Isso pode reduzir a carga no sistema e, assim, reduzir o uso da bateria.

Usar um WearableListenerService

Geralmente, é possível criar instâncias de WearableListenerService tanto no app para dispositivos portáteis quanto no app para wearables. No entanto, se você não quiser usar eventos de dados em um desses apps, não é necessário implementar o serviço nele.

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 ouve essas mudanças para atualizar a interface. Como a versão para wearables não atualiza nenhum item de dados, o app para dispositivos portáteis não ouve eventos de dados dela.

Alguns dos eventos que podem ser ouvidos 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 app fica 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.

Também é possível ouvir eventos do ChannelClient.ChannelCallback, como onChannelOpened().

Todos os eventos acima são executados em uma linha de execução em segundo plano, e 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. Isso permite que o sistema vincule o serviço conforme necessário.

O exemplo abaixo 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" 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>

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. Para isso, é necessário definir um caractere curinga ou um host específico. Caso contrário, o caminho definido vai ser ignorado pelo sistema.

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

Tenha em mente duas regras importantes ao fazer a correspondência de filtros de intent:

  • Se nenhum esquema for 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.

Usar 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 ouvir eventos em uma atividade implementando uma ou mais das interfaces abaixo:

Para criar uma atividade que ouve eventos de dados, faça o seguinte:

  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

Como mencionado anteriormente, 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() dessa atividade. Implementar listeners dessa maneira permite que o app receba eventos de forma mais seletiva, melhorando o design e a eficiência.