Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Acceso a la red y sincronización en Wear

Con Wear OS by Google, un reloj se puede comunicar con una red directamente sin acceder a un teléfono con Android o iOS. Este acceso directo a la red reemplaza el uso (en Wear 1.x) de la API de Data Layer para conectarse a una red.

Consulta los siguientes recursos relacionados:

Acceso a la red

Las apps para Wear OS pueden realizar solicitudes de red. Cuando un reloj tiene una conexión Bluetooth a un teléfono, el tráfico de red del reloj generalmente se envía mediante proxy a través del teléfono. Pero cuando un teléfono no está disponible, se usan las redes Wi-Fi y móviles, según el hardware. La plataforma de Wear maneja las transiciones entre redes.

Puedes usar protocolos como HTTP, TCP y UDP. No obstante, las API de android.webkit (incluida la clase CookieManager) no están disponibles. Puedes usar cookies con la lectura y escritura de encabezados en solicitudes y respuestas.

Además, recomendamos usar lo siguiente:

  • La API de JobScheduler para tareas asíncronas, incluido el sondeo en intervalos regulares (proceso que se describe más abajo)
  • API para varias redes si necesitas conectarte a tipos específicos de red (consulta Varias conexiones de red)

Acceso a redes de alto ancho de banda

La plataforma de Wear OS administra la conexión de red con el objetivo de proporcionar la mejor experiencia del usuario en general. Para elegir la red activa predeterminada, la plataforma evalúa dos factores:

  • La necesidad de conservar batería
  • La necesidad de ancho de banda de red

Cuando se prioriza la conservación de la batería, la red activa podría no llegar a satisfacer la necesidad de ancho de banda de ciertas tareas, como la transferencia de archivos grandes o la transmisión de contenido multimedia.

En esta sección, verás una guía para usar la clase ConnectivityManager y así asegurarte de que tu app pueda usar el ancho de banda de red necesario. Para obtener información general sobre el control preciso de recursos de red, lee el artículo Cómo administrar el uso de red.

También consulta el ejemplo que muestra las prácticas descritas a continuación.

Cómo adquirir una red de alto ancho de banda

En Wear OS, no debes dar por sentado que siempre hay una red de alto ancho de banda disponible. En los casos prácticos en los que se requiere acceso a redes de alto ancho de banda, como la transferencia de archivos grandes o la transmisión de contenido multimedia, recomendamos seguir estos pasos:

  1. Busca una red activa y, si la encuentras, revisa su ancho de banda.
  2. Si no encuentras una red activa, o si su ancho de banda es insuficiente, solicita acceso a una red no medida Wi-Fi o móvil.

Puedes usar la clase ConnectivityManager para comprobar si existe una red activa y si tiene ancho 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
    }
    

Puedes solicitar una red no medida de alto ancho de banda con ConnectivityManager. Basta con una solicitud para obtener una red no medida Wi-Fi o móvil. Cuando la red está lista (p. ej., la radio Wi-Fi del dispositivo se conecta a una red guardada), se llama al método onAvailable() de tu instancia NetworkCallback. Si no se encuentra una red adecuada, no se llama al método onAvailable(). Por lo tanto, deberías agotar manualmente el tiempo de espera de tu solicitud. Consulta Espera a que una red esté disponible.

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

Libera la red

Cuando tu app ya no necesita la red de alto ancho de banda, debes liberarla con la clase ConnectivityManager para asegurarte de que la plataforma pueda volver a administrar el acceso a la red.

Kotlin

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

Java

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

Para optimizar el consumo de la batería, una conexión de red solo debe permanecer registrada mientras se desarrolla la actividad. Por lo tanto, considera liberar la red en el método onStop() de tu actividad.

Espera a que una red esté disponible

Es posible que adquirir una red no sea un proceso instantáneo porque la radio móvil o Wi-Fi de un reloj podría estar desactivada para conservar batería. Además, si un reloj no se puede conectar a una red, no se llamará al método onAvailable() de tu instancia NetworkCallback. Por lo tanto, deberías agotar el tiempo de espera de la solicitud luego de un período predeterminado y liberar los recursos asociados que correspondan.

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

Controla el estado de la red

La interfaz NetworkCallback cuenta con métodos para controlar los cambios de estado de la red vinculada, por ejemplo, modificaciones en el ancho de banda y pérdida de conectividad.

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

Inicia la actividad de configuración de Wi-Fi

Cuando se solicita una red Wi-Fi, el sistema intenta conectarse a una red guardada si se había configurado alguna y si está dentro del alcance. No obstante, si no hay una red Wi-Fi disponible, no se llamará al método de devolución de llamada onAvailable() de tu instancia NetworkCallback. Si usas un Handler para agotar el tiempo de espera de la solicitud de red, puedes indicarle al usuario que agregue una red Wi-Fi cuando haya transcurrido el tiempo de espera. Puedes enviar al usuario directamente a la actividad para agregar una red Wi-Fi con el siguiente 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 la actividad de configuración, tu app debe tener el siguiente permiso: android.permission.CHANGE_WIFI_STATE

Consideraciones sobre la interfaz de usuario

Si tu app requiere una conexión a una nueva red Wi-Fi para una operación de alto ancho de banda, asegúrate de que el motivo de la conexión sea claro para el usuario antes que inicies la configuración de Wi-Fi. Solo debes solicitar que el usuario agregue una nueva red Wi-Fi cuando se requiera la red de alto ancho de banda. No impidas que un usuario acceda a las funciones de la app que no necesitan una red de alto ancho de banda.

En la figura 1 se muestra, por ejemplo, una app de música. La app debería permitirle al usuario explorar música y solicitarle que agregue una nueva red Wi-Fi únicamente si quiere descargar o transmitir música.

Descarga de música

Figura 1: El flujo de una app de música para descargar contenido musical

Si tu app requiere una red de alto ancho de banda para funcionar, debes presentar una explicación clara al usuario antes de pedirle que agregue una nueva red Wi-Fi. Además, para operaciones de red prolongadas, como descargar la playlist de contenido multimedia de un usuario, debes mostrar un indicador de progreso con una descripción de la operación que se está realizando.

En la figura 2, se muestra la app de música en un flujo de transmisión de contenido musical. Si un usuario desea transmitir música y se necesita una red de alto ancho de banda, la app debe explicar claramente el motivo antes de llevar al usuario a la configuración de Wi-Fi.

Transmisión de música

Figura 2: El flujo de una app de música para transmitir contenido musical

Envío de mensajes a través de la nube

Para enviar notificaciones, las apps pueden usar directamente Firebase Cloud Messaging (FCM), que reemplaza a Google Cloud Messaging (GCM). FCM es compatible con Wear 2.0, mientras que GCM no.

No hay API para acceso a redes o a FCM que sean específicas de Wear OS. Consulta la documentación existente sobre cómo conectarse a una red y el envío de mensajes a través de la nube.

FCM funciona bien con Descanso y es el método recomendado para enviar notificaciones a un reloj.

Proporciona mensajes de FCM mediante la recopilación de un token de registro para un dispositivo cuando se ejecute tu app para Wear. Luego, incluye el token como parte del destino cuando tu servidor envíe mensajes al extremo REST de FCM. FCM envía mensajes al dispositivo que identificó el token.

Un mensaje de FCM está en formato JSON y puede incluir una de las siguientes cargas útiles o ambas:

  • Carga útil de la notificación: Cuando un reloj recibe la carga útil de una notificación, los datos se muestran directamente al usuario en el flujo de notificaciones. Cuando el usuario presiona la notificación, se inicia tu app.
  • Carga útil de datos: La carga útil tiene un conjunto de pares clave-valor personalizados. La carga útil se entrega como datos a tu app para Wear.

Para obtener más información y ver ejemplos relacionados con cargas útiles, consulta Información sobre los mensajes de FCM.

De manera predeterminada, los mensajes se comparten desde una app para teléfonos a un reloj. Si tienes una app independiente para Wear y una app para teléfonos correspondiente, pueden generarse notificaciones duplicadas. Por ejemplo, un teléfono y un reloj que recibieron la misma notificación de FCM podrían mostrarla de forma independiente.

Usa servicios en segundo plano

Para garantizar que las tareas en segundo plano se ejecuten correctamente, deben tener en cuenta la función Descanso. En Android 6.0, Descanso y App Standby mejoraron la duración de batería.

Se mejoró la función Descanso en Android Nougat y Wear OS. Cuando una pantalla se apaga o ingresa al modo ambiente durante un tiempo suficientemente largo, puede producirse una instancia de Descanso y las tareas en segundo plano pueden posponerse durante ciertos períodos. Más tarde, cuando un dispositivo permanece inactivo durante un tiempo prolongado, aparece la función Descanso normal.

Deberías programar trabajos con la API de JobScheduler, que permite que tu app se registre para ejecutar código a prueba de Descanso. Cuando programas trabajos, puedes elegir restricciones; por ejemplo, que el trabajo se ejecute periódicamente, que se requiera conectividad o que el dispositivo esté cargado. Configura trabajos de manera tal que no perjudiquen la duración de batería. Usa el objeto JobInfo.Builder en trabajos para proporcionar restricciones y metadatos, p. ej., con uno o más de los siguientes métodos para una tarea:

  • Para programar una tarea que necesita una red, usa setRequiredNetworkType(int networkType) y especifica NETWORK_TYPE_ANY o NETWORK_TYPE_UNMETERED. Ten en cuenta que NETWORK_TYPE_UNMETERED es para transferencias grandes de datos, mientras que NETWORK_TYPE_ANY es para transferencias pequeñas.
  • Para programar una tarea mientras el dispositivo se carga, usa setRequiresCharging(boolean requiresCharging).
  • Para especificar que un dispositivo está inactivo a la hora de realizar una tarea, usa setRequiresDeviceIdle(boolean requiresDeviceIdle). Este método es útil para la sincronización o los trabajos en segundo plano de baja prioridad, en especial cuando se usa con setRequiresCharging.

Ten en cuenta que algunas redes de bajo ancho de banda, como Bluetooth de bajo consumo, se consideran de uso medido.

Programa tareas con restricciones

Puedes programar una tarea que incluya restricciones. En el ejemplo a continuación, un objeto JobScheduler activa MyJobService cuando se cumplen las siguientes restricciones:

  • Red no medida
  • Dispositivo en carga

Puedes usar el método del generador setExtras para adjuntar un conjunto de metadatos específicos de la app a la solicitud del trabajo. Cuando este se ejecuta, se proporciona el paquete a tu servicio de trabajo. Ten en cuenta el valor MY_JOB_ID que se pasa al constructor JobInfo.Builder. Este valor MY_JOB_ID es un identificador que proporciona la app. Las llamadas subsiguientes para cancelar y los trabajos posteriores creados con ese mismo valor actualizarán el trabajo 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);
    

Más abajo encontrarás una implementación de JobService para manejar el trabajo anterior. Cuando se ejecuta el trabajo, se pasa un objeto JobParameters al método onStartJob. El objeto JobParameters te permite obtener el valor del ID de trabajo junto con cualquier paquete de extras provisto al programar el trabajo. Como el método onStartJob se llama en el subproceso principal de la aplicación, cualquier lógica que consuma muchos recursos debe ejecutarse desde un subproceso separado. En el ejemplo, se usa AsyncTask para ejecutar código en segundo plano. Cuando finalice el trabajo, llamarás al método jobFinished para informar a JobScheduler que se completó la tarea:

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