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, no se puede seguir usando el objeto Network; 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 indican a tu app lo que sucede después de una llamada. Por lo tanto, no te permiten actualizar la IU ni ajustar el comportamiento de la app según la desconexión de la red o el cambio de capacidades de esta.

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

Cómo obtener un estado instantáneo

Un dispositivo Android puede mantener muchas conexiones al mismo tiempo. Primero, debes obtener 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);

Consultar estados instantáneos no es útil para la mayoría de las apps, excepto para realizar una depuración. A fin de obtener una funcionalidad más útil, como recibir alertas cuando se encuentran redes nuevas y se desconectan otras, registra una NetworkCallback. Si quieres obtener más información para registrar devoluciones de llamadas de redes, consulta Cómo escuchar los 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, pero también pueden incluir las VPN 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: Esta capacidad 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: Esta capacidad indica que la red no es una red privada virtual.

  • NET_CAPABILITY_VALIDATED: Esta capacidad indica que se descubrió que la red proporciona acceso real a la conexión de Internet pública la última vez que se realizó un sondeo. 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: Esta capacidad indica que la red tiene un portal cautivo, que se descubrió la última vez que se realizó un sondeo.

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

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 a fin de 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 escuchar los eventos de red

A fin de 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. El sistema decide cuál debería ser la predeterminada. 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 mediante la red predeterminada. Las apps también pueden enviar tráfico con otras redes. Para obtener más información, consulta la sección 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 conectaría a este punto de acceso y cambiaría la red predeterminada para 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. Se puede haber desconectado o no.

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. Consulta la sección NetworkCapabilities y LinkProperties para obtener más información.

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 aplicaciones pueden servirles otras redes disponibles. A fin de 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. La diferencia principal es que, 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. Por ejemplo, si tu app solo busca conexiones a Internet de uso 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 para no cargar el subproceso Connectivity de tu app. Llama a ConnectivityManager.unregisterNetworkCallback(NetworkCallback) cuando la devolución de llamada deja de ser 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 escribes tu app, debes verificar los valores predeterminados para ver si coinciden con tu caso de uso y borrarlos si la app también debe recibir notificaciones sobre redes que no tengan esas capacidades. Por el contrario, 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, deberías agregar NET_CAPABILITY_MMS a su NetworkRequest para evitar que reciba notificaciones sobre todas las redes que no pueden enviar MMS, o bien TRANSPORT_WIFI_AWARE si solo le interesa la conectividad de 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

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 esta sección, 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 hubiera otra red disponible, la app también recibiría 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 se hubiera registrado otra red no predeterminada, la app no habría recibido llamadas para esa 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 incluirían la capacidad NET_CAPABILITY_VALIDATED. Después de un período breve, recibiría una llamada a onNetworkCapabilitiesChanged(), y las capacidades nuevas incluirían 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 la configuración estaba desactivada, 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 la configuración Datos móviles siempre activos está inhabilitada, cuando se desconecte la conexión Wi-Fi, el dispositivo intentará volver a conectarse a la red móvil. La imagen sería 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 recientemente.

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 con Internet (consulta NET_CAPABILITY_INTERNET y NET_CAPABILITY_VALIDATED para verificar las opciones), y es posible que algunas redes estén restringidas a apps con privilegios.

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 cuentan con este permiso también pueden pedir al sistema que busque una red que no haya aparecido, como la red móvil, cuando el dispositivo está conectado a una red Wi-Fi. Este tipo de apps llaman a ConnectivityManager.requestNetwork(NetworkRequest, NetworkCallback) con un elemento NetworkCallback al que se puede llamar cuando se muestre la red.