Cómo leer el estado de la red

Android permite que las apps obtengan información sobre los cambios dinámicos de la conectividad. Usa las siguientes clases para realizar un seguimiento de los cambios de conectividad y responder a ellos:

  • ConnectivityManager le informa a tu app sobre el estado de conectividad del sistema.
  • La clase Network representa una de las redes a las que está conectado actualmente el dispositivo. Puedes usar el objeto Network como clave para recopilar información sobre la red con ConnectivityManager o para vincular sockets en la red. Cuando se desconecta la red, el objeto Network deja de usarse. Incluso si el dispositivo se vuelve a conectar al mismo aparato más tarde, un objeto Network nuevo representará la red nueva.
  • El objeto LinkProperties contiene información sobre el vínculo para una red, como la lista de servidores DNS, direcciones IP locales y rutas instaladas para la red.
  • El objeto NetworkCapabilities incluye información sobre las propiedades de una red, como los transportes (Wi-Fi, datos móviles, Bluetooth) y lo que la red puede hacer. Por ejemplo, puedes consultar el objeto para determinar si la red puede enviar MMS, si está protegida por un portal cautivo y si es de uso medido.

Las apps interesadas en el estado inmediato de conectividad en cualquier momento pueden llamar a los métodos de ConnectivityManager para saber qué tipo de red está disponible. Estos métodos son útiles para llevar a cabo la depuración y, a veces, para revisar un resumen de conectividad disponible en cualquier momento.

Sin embargo, los métodos síncronos de ConnectivityManager no le informan a tu app sobre lo que sucede después de una llamada. Por lo tanto, no te permiten actualizar la IU. Tampoco pueden ajustar el comportamiento de la app en función de la desconexión de la red o cuando cambian sus capacidades.

La conectividad puede cambiar en cualquier momento y la mayoría de las apps necesitan tener una vista actualizada del estado de red en el dispositivo Las apps pueden registrar una devolución de llamada con ConnectivityManager para recibir alertas sobre los cambios que le interesan a la app. Con la devolución de llamada, la app puede reaccionar de inmediato a cualquier cambio relevante en la conectividad sin tener que recurrir a encuestas costosas que podrían perderse actualizaciones rápidas.

No necesitas ningún permiso en particular para usar NetworkCallback y otras formas de conocer el estado de conectividad del dispositivo. Sin embargo, es posible que el uso de algunas redes esté sujeto a permisos específicos. Por ejemplo, algunas redes restringidas pueden no estar disponibles para las apps. La vinculación a una red en segundo plano requiere el permiso CHANGE_NETWORK_STATE. Además, algunas llamadas pueden solicitar permisos específicos para ejecutarse. Consulta la documentación correspondiente a cada llamada para conocer los detalles.

Cómo obtener estado instantáneo

Un dispositivo Android puede mantener muchas conexiones al mismo tiempo. Para obtener información sobre el estado de la red actual, primero obtén una instancia de ConnectivityManager:

Kotlin

val connectivityManager = getSystemService(ConnectivityManager::class.java)

Java

ConnectivityManager connectivityManager = getSystemService(ConnectivityManager.class);

Usa esta instancia para obtener una referencia a la red predeterminada actual para tu app:

Kotlin

val currentNetwork = connectivityManager.getActiveNetwork()

Java

Network currentNetwork = connectivityManager.getActiveNetwork();

Con una referencia a una red, tu app puede consultar información sobre ella:

Kotlin

val caps = connectivityManager.getNetworkCapabilities(currentNetwork)
val linkProperties = connectivityManager.getLinkProperties(currentNetwork)

Java

NetworkCapabilities caps = connectivityManager.getNetworkCapabilities(currentNetwork);
LinkProperties linkProperties = connectivityManager.getLinkProperties(currentNetwork);

Para obtener una funcionalidad más útil, registra un objeto NetworkCallback. Para obtener más información sobre el registro de devoluciones de llamada de red, consulta el artículo sobre detección de eventos de red.

NetworkCapabilities y LinkProperties

Los objetos NetworkCapabilities y LinkProperties proporcionan información sobre todos los atributos que el sistema conoce de una red.

El objeto LinkProperties conoce las rutas, las direcciones de vínculos, el nombre de la interfaz, la información del proxy (si existe) y los servidores DNS. Llama al método relevante en el objeto LinkProperties para recuperar la información que necesites.

El objeto NetworkCapabilities encapsula información sobre el transporte de redes y sus capacidades.

Un transporte es una abstracción de un medio físico sobre el que opera la red. Algunos ejemplos comunes de transporte son Ethernet, Wi-Fi y redes móviles. También pueden incluir las VPNs o las conexiones Wi-Fi entre pares. En Android, una red puede tener varios transportes al mismo tiempo. Un ejemplo de esto sería una VPN que opera a través de Wi-Fi y redes móviles, por lo que tendría los transportes de Wi-Fi, red móvil y VPN. Para descubrir si una red tiene un transporte en particular, usa NetworkCapabilities.hasTransport(int) con una de las constantes NetworkCapabilities.TRANSPORT_*.

Una capacidad describe una propiedad de la red. Entre los ejemplos de capacidades, se incluyen MMS, NOT_METERED y INTERNET. Una red con la capacidad de MMS puede enviar y recibir mensajes de servicios de mensajería multimedia, mientras que una red sin esta capacidad no puede. Una red con la capacidad NOT_METERED no factura al usuario por el uso de datos. Tu app puede comprobar si hay capacidades apropiadas usando el método NetworkCapabilities.hasCapability(int) con una de las constantes NetworkCapabilities.NET_CAPABILITY_*.

Entre las constantes NET_CAPABILITY_* más útiles, se incluyen las siguientes:

  • NET_CAPABILITY_INTERNET: Esta capacidad indica que la red está configurada para acceder a Internet. Ten en cuenta que se trata de una configuración y no de la capacidad real para llegar a los servidores públicos. Por ejemplo, una red se puede configurar para acceder a Internet, pero estar sujeta a un portal cautivo.

    La red móvil de un proveedor generalmente tiene la capacidad INTERNET, mientras que una red Wi-Fi P2P local no suele tenerla. Para obtener detalles sobre la conectividad real, consulta NET_CAPABILITY_VALIDATED.

  • NET_CAPABILITY_NOT_METERED: Indica que la red no es de uso medido. Una red se clasifica como de uso medido cuando el usuario es susceptible a un importante uso de datos con esa conexión debido a los costos monetarios, las limitaciones de datos o los problemas de batería o rendimiento.

  • NET_CAPABILITY_NOT_VPN: Indica que la red no es una red privada virtual.

  • NET_CAPABILITY_VALIDATED: Indica que la red proporciona acceso real a la Internet pública cuando se sondea. Las redes protegidas por un portal cautivo o las que no proporcionan una resolución de nombre de dominio no tienen esta función. Esta es la información más precisa que puede obtener el sistema sobre el acceso que proporciona una red, aunque, en principio, una red validada puede estar sujeta al filtro basado en IP o sufrir pérdidas repentinas de conectividad debido a problemas como una señal débil.

  • NET_CAPABILITY_CAPTIVE_PORTAL: Indica que la red tiene un portal cautivo cuando se sondea.

Existen otras capacidades que pueden interesarle a apps más especializadas. Para obtener más información, lee las definiciones de los parámetros en NetworkCapabilities.hasCapability(int).

Las capacidades de una red pueden cambiar en cualquier momento. Cuando el sistema detecta un portal cautivo, le muestra una notificación al usuario para que acceda. Mientras este proceso está en curso, la red tiene las capacidades NET_CAPABILITY_INTERNET y NET_CAPABILITY_CAPTIVE_PORTAL, pero no NET_CAPABILITY_VALIDATED.

Cuando el usuario decide acceder a la página del portal cautivo, el dispositivo puede acceder a la conexión de Internet pública, y la red obtiene la capacidad NET_CAPABILITY_VALIDATED y pierde la capacidad NET_CAPABILITY_CAPTIVE_PORTAL.

De forma similar, los transportes de una red pueden cambiar de forma dinámica. Por ejemplo, una VPN puede reconfigurarse para usar una red más rápida que haya encontrado, como cambiar de una red móvil a Wi-Fi para su red subyacente. En este caso, la red pierde el transporte TRANSPORT_CELLULAR y obtiene el transporte TRANSPORT_WIFI, mientras conserva TRANSPORT_VPN.

Cómo detectar eventos de red

Para obtener información sobre los eventos de red, usa la clase NetworkCallback junto con ConnectivityManager.registerDefaultNetworkCallback(NetworkCallback) y ConnectivityManager.registerNetworkCallback(NetworkCallback). Estos dos métodos tienen propósitos distintos.

Todas las apps para Android tienen una red predeterminada por el sistema. Por lo general, se prefieren las redes no medidas a las medidas y las redes más rápidas antes que las más lentas.

Cuando una app emite una solicitud de red, por ejemplo, con HttpsURLConnection, el sistema cumple con esta solicitud a través de la red predeterminada. Las apps también pueden enviar tráfico con otras redes. Para obtener más información, consulta la sección sobre redes adicionales.

La red configurada como predeterminada puede cambiar en cualquier momento durante el ciclo de vida de una app. Un ejemplo típico sería cuando el dispositivo entra en el rango de un punto de acceso Wi-Fi conocido que esté activo, que no sea medido y que sea más rápido que la red móvil. El dispositivo se conecta a este punto de acceso y cambia la red predeterminada de todas las apps a la nueva red Wi-Fi.

Cuando una red nueva se convierte en la predeterminada, cualquier conexión nueva que abra la app usará esta red. En algún momento, todas las conexiones restantes con la red predeterminada anterior se anulan de manera forzada. Si es importante que la app sepa cuándo se cambia la red predeterminada, debe registrar una devolución de llamada de red predeterminada, como se muestra a continuación:

Kotlin

connectivityManager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network : Network) {
        Log.e(TAG, "The default network is now: " + network)
    }

    override fun onLost(network : Network) {
        Log.e(TAG, "The application no longer has a default network. The last default network was " + network)
    }

    override fun onCapabilitiesChanged(network : Network, networkCapabilities : NetworkCapabilities) {
        Log.e(TAG, "The default network changed capabilities: " + networkCapabilities)
    }

    override fun onLinkPropertiesChanged(network : Network, linkProperties : LinkProperties) {
        Log.e(TAG, "The default network changed link properties: " + linkProperties)
    }
})

Java

connectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        Log.e(TAG, "The default network is now: " + network);
    }

    @Override
    public void onLost(Network network) {
        Log.e(TAG, "The application no longer has a default network. The last default network was " + network);
    }

    @Override
    public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
        Log.e(TAG, "The default network changed capabilities: " + networkCapabilities);
    }

    @Override
    public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
        Log.e(TAG, "The default network changed link properties: " + linkProperties);
    }
});

Cuando una red nueva se convierte en la predeterminada, la app recibe una llamada a onAvailable(Network) para la red nueva. Implementa onCapabilitiesChanged(Network,NetworkCapabilities), onLinkPropertiesChanged(Network,LinkProperties) o ambos objetos para reaccionar de manera adecuada ante los cambios de conectividad.

Para una devolución de llamada registrada con registerDefaultNetworkCallback(), onLost() implica que la red perdió el estado de red predeterminada. Es posible que esté desconectado.

Si bien puedes obtener información sobre los transportes que utiliza la red predeterminada consultando NetworkCapabilities.hasTransport(int), este es un proxy deficiente para el ancho de banda o la medición de la red. Tu app no debe suponer que la red Wi-Fi es siempre de uso no medido y que brinda un ancho de banda mejor que la red móvil.

En su lugar, usa NetworkCapabilities.getLinkDownstreamBandwidthKbps() para medir el ancho de banda y NetworkCapabilites.hasCapability(int) con argumentos NET_CAPABILITY_NOT_METERED a fin de determinar la medición. Si quieres obtener más información, consulta la sección sobre NetworkCapabilities y LinkProperties.

De forma predeterminada, se llama a los métodos de devolución de llamada en el subproceso de conectividad de tu app, que es un subproceso independiente que usa ConnectivityManager. Si tu implementación de devoluciones de llamada necesita realizar trabajos más extensos, llámalas en un subproceso de trabajador separado con la variante ConnectivityManager.registerDefaultNetworkCallback(NetworkCallback, Handler).

Cancela el registro de la devolución de llamada cuando ya no la uses. Para ello, invoca a ConnectivityManager.unregisterNetworkCallback(NetworkCallback). El elemento onPause() de tu actividad principal es un buen lugar para hacerlo, en especial si registras la devolución de llamada en onResume().

Redes adicionales

Si bien la red predeterminada es la única red relevante para la mayoría de las apps, a algunas apps pueden servirles otras redes disponibles. Para descubrirlas, las apps crean una NetworkRequest que coincida con sus necesidades y llaman a ConnectivityManager.registerNetworkCallback(NetworkRequest, NetworkCallback).

El proceso es similar a la detección de una red predeterminada. Sin embargo, aunque solo puede haber una red predeterminada que se aplique a una app en cualquier momento, esta versión permite que tu app vea todas las redes disponibles de forma simultánea, por lo que una llamada a onLost(Network) significa que se desconectó la red para siempre, y no que dejó de ser la predeterminada.

La app crea una NetworkRequest para informar a ConnectivityManager sobre qué tipo de redes desea detectar. En el siguiente ejemplo, se muestra cómo compilar un NetworkRequest para una app que solo está interesada en las conexiones a Internet de uso no medido:

Kotlin

val request = NetworkRequest.Builder()
  .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
  .addCapability(NET_CAPABILITY_INTERNET)
  .build()

connectivityManager.registerNetworkCallback(request, myNetworkCallback)

Java

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

connectivityManager.registerNetworkCallback(request, myNetworkCallback);

Esto garantizaría que tu app detecte todos los cambios relacionados con cualquier red no medida del sistema.

En cuanto a la devolución de llamada de red predeterminada, hay una versión de registerNetworkCallback(NetworkRequest, NetworkCallback, Handler) que acepta un Handler de modo que no cargue el subproceso Connectivity de tu app.

Llama a ConnectivityManager.unregisterNetworkCallback(NetworkCallback) cuando la devolución de llamada ya no sea relevante. Una app puede registrar varias devoluciones de llamada de red de forma simultánea.

Para mayor comodidad, el objeto NetworkRequest contiene las funciones comunes que necesitan la mayoría de las apps, por ejemplo:

Cuando escribas tu app, verifica los valores predeterminados para ver si coinciden con tu caso de uso y bórralos si deseas que se le notifique a tu app sobre las redes que no tienen estas capacidades. Por otro lado, debes agregar capacidades que eviten que la app reciba llamadas luego de cualquier cambio de conectividad en las redes con las que no interactúa.

Por ejemplo, si tu app necesita enviar mensajes MMS, agrega NET_CAPABILITY_MMS a la NetworkRequest para evitar recibir información sobre todas las redes que no pueden enviar mensajes MMS. Agrega TRANSPORT_WIFI_AWARE si a tu app solo le interesa la conectividad Wi-Fi P2P. NET_CAPABILITY_INTERNET y NET_CAPABILITY_VALIDATED son elementos útiles si te interesa la capacidad de transferir datos con un servidor en Internet.

Ejemplo de secuencia de devolución de llamada

En esta sección, se describe la secuencia de devoluciones de llamada que una app puede recibir si registra una devolución de llamada predeterminada y una normal en un dispositivo que actualmente tiene conectividad móvil. En este ejemplo, el dispositivo se conecta a un buen punto de acceso Wi-Fi y, luego, se desconecta de él. En este ejemplo, también se supone que el dispositivo tiene habilitada la configuración Datos móviles siempre activos.

A continuación, se describe el cronograma:

  1. Cuando la app llama a registerNetworkCallback(), la devolución de llamada recibe inmediatamente llamadas de onAvailable(), onNetworkCapabilitiesChanged() y onLinkPropertiesChanged() para la red móvil porque es la única disponible. Si hay otra red disponible, la app también recibe devoluciones de llamada para la otra red.

    Diagrama de estado que muestra el evento de devolución de llamada para registrar una red y las devoluciones de llamada que activa el evento
    Figura 1: Estado de la app después de llamar al elemento registerNetworkCallback()

  2. Luego, la app llama al elemento registerDefaultNetworkCallback(). La devolución de llamada de red predeterminada comienza a recibir llamadas a onAvailable(), onNetworkCapabilitiesChanged() y onLinkPropertiesChanged() para la red móvil porque esta es la predeterminada. Si otra red no predeterminada está activa, la app no puede recibir llamadas de esta red.

    Diagrama de estado que muestra el registro del evento de devolución de llamada para una red predeterminada y las devoluciones de llamada que activa el evento
    Figura 2: Estado de la app después de registrar una red predeterminada

  3. Luego, el dispositivo se conecta a una red Wi-Fi (de uso no medido). La devolución de llamada de red normal recibe llamadas a onAvailable(), onNetworkCapabilitiesChanged() y onLinkPropertiesChanged() para la red Wi-Fi.

    Diagrama de estado que muestra las devoluciones de llamada que se activan cuando la app se conecta a una red nueva
    Figura 3: Estado de la app después de conectarse a una red Wi-Fi de uso no medido

  4. A esta altura, es posible que la red Wi-Fi demore un poco en validarse. En este caso, las llamadas de onNetworkCapabilitiesChanged() para la devolución de llamada de red normal no incluyen la capacidad NET_CAPABILITY_VALIDATED. Después de un período breve, recibe una llamada a onNetworkCapabilitiesChanged(), y las capacidades nuevas incluyen NET_CAPABILITY_VALIDATED. En la mayoría de los casos, el proceso de validación es muy rápido.

    Cuando se valida la red Wi-Fi, el sistema la prefiere antes que la red móvil, principalmente porque es de uso no medido. La red Wi-Fi se convierte en la red predeterminada, por lo que la devolución de llamada de red predeterminada recibe una llamada a onAvailable(), onNetworkCapabilitiesChanged() y onLinkPropertiesChanged() para la red Wi-Fi. La red móvil pasa a segundo plano, y la devolución de llamada de red normal recibe una llamada a onLosing() para la red móvil.

    Dado que en este ejemplo se supone que los datos móviles siempre están activados para este dispositivo, la red móvil nunca se desconecta. Si el parámetro estaba desactivado, después de un tiempo se desconecta la red móvil, y la devolución de llamada de red normal recibe una llamada a onLost().

    Diagrama de estado que muestra las devoluciones de llamada que se activan cuando se valida una conexión de red Wi-Fi
    Figura 4: Estado de la app después de que se valida la red Wi-Fi

  5. Más adelante, el dispositivo se desconecta repentinamente de la red Wi-Fi porque quedó fuera de alcance. Dado que se desconectó la red Wi-Fi, la devolución de llamada de red normal recibe una llamada a onLost() para la conexión Wi-Fi. Como la red móvil es la nueva red predeterminada, la devolución de llamada de red predeterminada recibe llamadas a onAvailable(), onNetworkCapabilitiesChanged() y onLinkPropertiesChanged() para la red móvil.

    Diagrama de estado que muestra las devoluciones de llamada que se activan cuando se pierde la conexión a una red Wi-Fi
    Figura 5: Estado de la app después de que se desconecta la red Wi-Fi

Si el parámetro de datos móviles siempre activos está inhabilitado, cuando se desconecte la conexión Wi-Fi, el dispositivo intentará volver a conectarse a la red móvil. La imagen es similar, pero con una breve demora adicional para las llamadas de onAvailable(), y la devolución de llamada de red normal también recibiría llamadas a onAvailable(), onNetworkCapabilitiesChanged() y onLinkPropertiesChanged(), ya que la red móvil estaría disponible.

Restricciones en el uso de la red para la transferencia de datos

El hecho de que puedas ver una red con una devolución de llamada de red no significa que tu app pueda usarla para transferir datos. Algunas redes no proporcionan conectividad a Internet, y otras pueden estar restringidas a apps con privilegios. Para comprobar la conectividad a Internet, consulta NET_CAPABILITY_INTERNET y NET_CAPABILITY_VALIDATED.

El uso de redes en segundo plano también está sujeto a la verificación de permisos. Si tu app desea usar una red en segundo plano, necesitará el permiso CHANGE_NETWORK_STATE.

Las apps que tienen este permiso permiten que el sistema intente abrir una red que no está activa, como la red móvil, cuando el dispositivo está conectado a una red Wi-Fi. Esta app llama a ConnectivityManager.requestNetwork(NetworkRequest, NetworkCallback) con un elemento NetworkCallback al que se puede llamar cuando se abre la red.