Acesso à rede e sincronização no Wear

Com o Wear OS by Google, um smartwatch pode se conectar diretamente a uma rede, sem acesso a um smartphone Android ou iOS. Esse acesso direto substitui o uso (no Wear 1.x) da API Data Layer para se conectar a uma rede.

Acesso à rede

Os apps Wear OS podem fazer solicitações de rede. Quando um smartwatch tem conexão Bluetooth com um smartphone, o tráfego de rede do relógio geralmente é colocado em proxy pelo smartphone. Porém, quando o smartphone não está disponível, as redes celulares e Wi-Fi são usadas, dependendo do hardware. A plataforma Wear OS processa as transições entre redes.

Você pode usar protocolos como HTTP, TCP e UDP. Entretanto, as APIs android.webkit, incluindo a classe CookieManager, não estão disponíveis. Você pode usar cookies lendo e gravando cabeçalhos em solicitações e respostas.

Além disso, recomendamos que você use o seguinte:

  • A API JobScheduler para tarefas assíncronas, incluindo pesquisas em intervalos regulares (descritos abaixo).
  • APIs para várias redes, se você precisar se conectar a tipos de rede específicos. Consulte Diversas conexões de rede.

Acesso à rede de alta largura de banda

A plataforma Wear OS gerencia a conectividade de rede para proporcionar a melhor experiência geral do usuário. A plataforma escolhe a rede padrão ativa considerando dois fatores:

  • Necessidade de uma longa duração da bateria
  • Necessidade de largura de banda da rede

Quando a economia da bateria é priorizada, a rede ativa pode ter largura de banda insuficiente para realizar tarefas de rede que exijam alta largura de banda, como enviar arquivos grandes ou fazer streaming de mídia.

Esta seção traz instruções sobre o uso da classe ConnectivityManager para garantir que a largura de banda necessária esteja disponível para seu app. Para ver informações gerais sobre controle detalhado de recursos de rede, consulte Gerenciar o uso da rede.

Adquirir uma rede de alta largura de banda

No Wear OS, não presuma que uma rede de alta largura de banda sempre estará disponível. Para casos que precisam de acesso a uma rede de alta largura de banda, como envio de arquivos grandes ou streaming de mídia, recomendamos que você siga os seguintes passos:

  1. Verifique se há uma rede ativa. Caso haja alguma, verifique a largura de banda.
  2. Se não houver nenhuma rede ativa ou se a largura de banda for insuficiente, solicite acesso a uma rede celular ou Wi-Fi ilimitada.

Você pode usar a classe ConnectivityManager para verificar se há alguma rede ativa e se ela tem largura de banda suficiente:

Kotlin

const val MIN_BANDWIDTH_KBPS = 320
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val bandwidth: Int = connectivityManager.activeNetwork?.let { activeNetwork ->
    connectivityManager.getNetworkCapabilities(activeNetwork).linkDownstreamBandwidthKbps
} ?: -1

when {
    bandwidth < 0 -> {
        // No active network
    }
    bandwidth in (0 until MIN_BANDWIDTH_KBPS) -> {
        // Request a high-bandwidth network
    }
    else -> {
        // You already are on a high-bandwidth network, so start your network request
    }
}

Java

int MIN_BANDWIDTH_KBPS = 320;
connectivityManager =
  (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
Network activeNetwork = connectivityManager.getActiveNetwork();

if (activeNetwork != null) {
  int bandwidth =
    connectivityManager.getNetworkCapabilities(activeNetwork).getLinkDownstreamBandwidthKbps();

  if (bandwidth < MIN_BANDWIDTH_KBPS) {
    // Request a high-bandwidth network
  } else {
    // You already are on a high-bandwidth network, so start your network request
  }
} else {
  // No active network
}

Você pode solicitar uma rede de alta largura de banda ilimitada usando o ConnectivityManager. Com uma única solicitação de rede, você pode solicitar uma rede celular ou Wi-Fi ilimitada. Quando a rede estiver pronta (por exemplo, se o rádio Wi-Fi do dispositivo se conectar a uma rede salva), o método onAvailable() da sua instância NetworkCallback será chamado. Se nenhuma rede adequada for encontrada, o método onAvailable() não será chamado. Por isso, você precisará definir o tempo limite da sua solicitação manualmente. Consulte Aguardar a disponibilidade de rede.

Kotlin

networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        if (bindProcessToNetwork(network)) {
            // socket connections will now use this network
        } else {
            // app doesn't have android.permission.INTERNET permission
        }
    }
}

val request: NetworkRequest = NetworkRequest.Builder().run {
    addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
    addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
    addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    build()
}

connectivityManager.requestNetwork(request, networkCallback)

Java

networkCallback = new ConnectivityManager.NetworkCallback() {
  @Override
  public void onAvailable(Network network) {
    if (bindProcessToNetwork(network)) {
      // socket connections will now use this network
    } else {
      // app doesn't have android.permission.INTERNET permission
    }
  }
};

NetworkRequest request = new NetworkRequest.Builder()
  .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
  .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
  .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
  .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
  .build();

connectivityManager.requestNetwork(request, networkCallback);

Liberar rede

Quando seu app não precisar mais da rede de alta largura de banda, você precisará liberar a rede usando a classe ConnectivityManager para garantir que a plataforma possa retomar o gerenciamento do acesso à rede.

Kotlin

connectivityManager.bindProcessToNetwork(null)
connectivityManager.unregisterNetworkCallback(networkCallback)

Java

connectivityManager.bindProcessToNetwork(null);
connectivityManager.unregisterNetworkCallback(networkCallback);

Para otimizar o consumo da bateria, uma conexão de rede precisa ficar registrada somente durante a atividade. Por isso, recomendamos liberar a rede no método onStop() da sua atividade.

Aguardar a disponibilidade de rede

A aquisição de uma rede pode não ser instantânea, porque o rádio Wi-Fi ou celular de um smartphone pode estar desativado para economizar bateria. Além disso, se um relógio não puder se conectar a uma rede, o método onAvailable() da sua instância NetworkCallback não será chamado. Por isso, você precisa definir o tempo limite da solicitação após um período predeterminado e liberar os recursos associados.

Kotlin

const val MESSAGE_CONNECTIVITY_TIMEOUT = 1
const val NETWORK_CONNECTIVITY_TIMEOUT_MS: Long = 10000
...
handler = MyHandler()

networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        handler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT)
        ...
    }
}

connectivityManager.requestNetwork(request, networkCallback)

handler.sendMessageDelayed(
        handler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT),
        NETWORK_CONNECTIVITY_TIMEOUT_MS
)
...
// Don't make this an inner class otherwise there is the potential to leak the parent class
private class MyHandler : Handler() {
    override fun handleMessage(msg: Message) {
        when (msg.what) {
            MESSAGE_CONNECTIVITY_TIMEOUT -> {
                // unregister the network
            }
        }
    }
}

Java

int MESSAGE_CONNECTIVITY_TIMEOUT = 1;
long NETWORK_CONNECTIVITY_TIMEOUT_MS = 10000;

handler = new MyHandler();

networkCallback = new ConnectivityManager.NetworkCallback() {
  @Override
  public void onAvailable(Network network) {
    handler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT);
    ...
  }
};

connectivityManager.requestNetwork(request, networkCallback);

handler.sendMessageDelayed(
  handler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT),
  NETWORK_CONNECTIVITY_TIMEOUT_MS);

...
// This needs to be static to avoid potentially leaking the parent class
private static class MyHandler extends Handler {
   @Override
   public void handleMessage(Message msg) {
       switch (msg.what) {
           case MESSAGE_CONNECTIVITY_TIMEOUT:
               // unregister the network
               break;
       }
   }
}

Monitorar o status da rede

A interface NetworkCallback tem métodos para o monitoramento de mudanças de status da rede vinculada, como mudanças de largura de banda e perda de conectividade.

Kotlin

networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
        val bandwidth: Int = networkCapabilities.linkDownstreamBandwidthKbps

        if (bandwidth < MIN_BANDWIDTH_KBPS) {
            // handle insufficient network bandwidth
        }
    }

    override fun onLost(network: Network) {
        // handle network loss
    }
}

Java

networkCallback = ConnectivityManager.NetworkCallback {
  @Override
  public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
    int bandwidth = networkCapabilities.getLinkDownstreamBandwidthKbps();

      if (bandwidth < MIN_BANDWIDTH_KBPS) {
        // handle insufficient network bandwidth
      }
  }

  @Override
  public void onLost(Network network) {
    // handle network loss
  }
}

Iniciar a atividade de configuração de Wi-Fi

Ao solicitar uma rede Wi-Fi, o sistema tenta se conectar a uma rede salva se alguma tiver sido configurada e estiver dentro do alcance. No entanto, se nenhuma rede Wi-Fi salva estiver disponível, o método de callback onAvailable() para sua instância NetworkCallback nunca será chamado. Se você estiver usando um Handler para definir o tempo limite da solicitação de rede, será possível direcionar o usuário a uma rede Wi-Fi quando o tempo limite for atingido. Você pode levar o usuário diretamente ao usuário para adicionar uma rede Wi-Fi usando a seguinte intent:

Kotlin

context.startActivity(Intent("com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS"))

Java

context.startActivity(new Intent("com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS"));

Para iniciar a atividade de configuração, o app precisa ter a seguinte permissão: android.permission.CHANGE_WIFI_STATE

Considerações sobre a interface do usuário

Se seu app precisar de uma conexão com uma nova rede Wi-Fi para uma operação de alta largura de banda, verifique se o motivo para a conexão está claro para o usuário antes de iniciar as configurações de Wi-Fi. Só solicite que um usuário adicione uma nova rede Wi-Fi quando a rede de alta largura de banda for necessária. Não bloqueie o acesso de um usuário a recursos do app que não precisem de uma rede de alta largura de banda.

A Figura 1 mostra, por exemplo, um app de música. O app precisa permitir que o usuário procure músicas e só solicitar que ele adicione uma nova rede Wi-Fi se ele quiser fazer o download ou streaming de músicas.

Download de música

Figura 1. Fluxo de um app para download de música.

Se seu app precisar de uma rede de alta largura de banda para funcionar, você precisa mostrar uma justificativa clara antes de solicitar que o usuário adicione uma nova rede Wi-Fi. Além disso, para operações de rede longas, como o download de uma playlist de mídia, você precisa apresentar um indicador de progresso com uma descrição de que a operação está sendo realizada.

A Figura 2 mostra o app em um fluxo de streaming de música. Se o usuário quiser fazer streaming de músicas e precisar de uma rede de alta largura de banda, o app terá que explicar claramente por que a rede Wi-Fi é necessária antes de levar o usuário às configurações de Wi-Fi.

Streaming de música

Figura 2. Fluxo de um app para streaming de música.

Mensagens na nuvem

Para enviar notificações, os apps podem usar diretamente o Firebase Cloud Messaging (FCM), que substitui o Google Cloud Messaging (GCM). O FCM é compatível com o Wear 2.0 e o GCM não.

Nenhuma API para acesso à rede ou FCM é específica do Wear OS. Consulte a documentação existente sobre como se conectar a uma rede e sobre mensagens na nuvem.

O FCM funciona bem com a Soneca e é a forma recomendada de enviar notificações para um smartwatch.

Forneça mensagens do FCM coletando um token de registro para um dispositivo quando o app Wear for executado. Em seguida, inclua o token como parte do destino quando o servidor enviar mensagens para o endpoint REST do FCM. O FCM enviará mensagens para o dispositivo identificado pelo token.

As mensagens do FCM estão no formato JSON e podem incluir um dos seguintes payloads ou ambos:

  • Payload de notificação: Quando um payload de notificação é recebido por um smartwatch, os dados são exibidos ao usuário no stream de notificações. Quando o usuário toca na notificação, o app é iniciado.
  • Payload de dados: o payload tem um conjunto de pares de chave-valor personalizados. O payload é entregue como dados para o app Wear.

Para ver mais informações e exemplos de payloads, consulte Sobre as mensagens do FCM.

Por padrão, as notificações são compartilhadas de um app do smartphone para um smartwatch. Se você tiver um app Wear independente e um app de smartphones correspondente, pode haver notificações duplicadas. Por exemplo, a mesma notificação do FCM, recebida tanto pelo smartphone como pelo smartwatch, pode ser exibida por ambos os dispositivos de maneira independente.

Usar serviços em segundo plano

Para garantir que as tarefas em segundo plano sejam executadas corretamente, é preciso considerar a Soneca. No Android 6.0, a Soneca e o App em espera melhoraram a duração da bateria.

A Soneca foi aprimorada no Android Nougat e no Wear OS. Quando uma tela é desligada ou entra no modo ambiente por um certo período, pode ocorrer uma subconfiguração da Soneca, e tarefas em segundo plano podem ser adiadas por algum tempo. Depois, quando um dispositivo fica inativo por um tempo prolongado, a Soneca normal ocorre.

Você precisa programar tarefas com a API JobScheduler, que permite que o app registre uma execução de código que não seja afetada pela Soneca. Ao programar tarefas, você pode selecionar limites, como a execução periódica e a necessidade de conectividade ou carregamento do dispositivo. Configure tarefas de uma maneira que não afete negativamente a duração da bateria. As tarefas precisam usar um objeto JobInfo.Builder para criar limites e metadados, como um ou mais dos seguintes métodos para tarefa:

  • Para programar uma tarefa que exija rede, use setRequiredNetworkType(int networkType), especificando NETWORK_TYPE_ANY ou NETWORK_TYPE_UNMETERED. NETWORK_TYPE_UNMETERED é usado para grandes transferências de dados, enquanto NETWORK_TYPE_ANY é para transferências pequenas
  • Para programar uma tarefa durante o carregamento, use setRequiresCharging(boolean requiresCharging)
  • Para especificar que o dispositivo está inativo para uma tarefa, use setRequiresDeviceIdle(boolean requiresDeviceIdle). Esse método é útil para trabalhos ou sincronizações em segundo plano de baixa prioridade, especialmente quando usado com setRequiresCharging

Observe que algumas redes de baixa largura de banda, como Bluetooth LE, são consideradas limitadas.

Agendar restrições

Você pode programar uma tarefa que precise de restrições. No exemplo abaixo, um objeto JobScheduler ativa MyJobService quando as seguintes restrições são respeitadas:

  • Rede ilimitada
  • Carregamento do dispositivo

Você pode usar o método do builder setExtras para anexar um pacote de metadados específicos do app à solicitação de tarefa. Quando a tarefa for executada, esse pacote será fornecido ao serviço da tarefa. O valor MY_JOB_ID transmitido ao construtor JobInfo.Builder. Esse valor MY_JOB_ID é um identificador fornecido pelo app. Chamadas de cancelamento e tarefas subsequentes criadas com o mesmo valor atualizarão a tarefa existente:

Kotlin

JobInfo.Builder(MY_JOB_ID,
        ComponentName(this, MyJobService::class.java))
        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
        .setRequiresCharging(true)
        .setExtras(extras)
        .build()
        .also { jobInfo ->
            (getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler)
                    .schedule(jobInfo)
        }

Java

JobInfo jobInfo = new JobInfo.Builder(MY_JOB_ID,
        new ComponentName(this, MyJobService.class))
        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
        .setRequiresCharging(true)
        .setExtras(extras)
        .build();
((JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE))
        .schedule(jobInfo);

Abaixo, há uma implementação de um JobService para processar a tarefa acima. Quando a tarefa é executada, um objeto JobParameters é transmitido ao método onStartJob. O objeto JobParameters permite que você tenha acesso ao valor do ID da tarefa juntamente a quaisquer pacotes extras fornecidos ao programar a tarefa. O método onStartJob é chamado na linha de execução principal do aplicativo e, por isso, qualquer lógica de alto custo precisa ser executada em uma linha diferente. No exemplo, uma AsyncTask é usada para executar códigos em segundo plano. Quando o trabalho for concluído, você chamará o método jobFinished para notificar JobScheduler de que a tarefa foi concluída:

Kotlin

private class MyJobService : JobService() {

    override fun onStartJob(params: JobParameters): Boolean {
        JobAsyncTask().execute(params)
        return true
    }

    private class JobAsyncTask : AsyncTask<...>() { ... }
}

Java

public class MyJobService extends JobService {
    @Override public boolean onStartJob(JobParameters params) {
        new JobAsyncTask().execute(params);
        return true;
    }

    private class JobAsyncTask extends AsyncTask