Cómo optimizar las descargas para lograr un acceso de red eficiente

El uso de la radio inalámbrica para transferir datos es quizás una de las fuentes más importantes de consumo de la batería de tu app. Con el fin de minimizar el consumo de la batería asociado con la actividad de red, es esencial que comprendas cómo el modelo de conectividad afecta al hardware subyacente de la radio.

En esta lección, se presenta la máquina de estado de la radio inalámbrica y se explica cómo el modelo de conectividad de tu app interactúa con ella. A continuación, se proponen formas de minimizar las conexiones de datos, usar la carga previa y agrupar las transferencias para minimizar el consumo de la batería asociado con las transferencias de datos.

Máquina de estado de radio

Una radio inalámbrica totalmente activa consume una gran cantidad de energía, por lo que transita entre diferentes estados con el fin de conservar la energía cuando no está en uso, mientras se intenta minimizar la latencia asociada con el "encendido" de la radio cuando es necesario.

La máquina de estado de una radio de red 3G típica consta de tres estados de energía:

  1. Energía completa: Se utiliza cuando una conexión está activa, lo que permite que el dispositivo transfiera datos a la máxima velocidad posible.

  2. Energía baja: Se trata de un estado intermedio que usa aproximadamente el 50% de la energía de la batería en estado completo.

  3. En espera: Es el estado de energía mínimo durante el cual no se requiere ni se activa ninguna conexión de red.

Si bien los estados "energía baja" y "en espera" consumen mucha menos batería, también presentan una latencia significativa para las solicitudes de red. Regresar al estado de energía completa desde el estado de energía baja lleva aproximadamente 1.5 segundos, mientras que pasar del modo de espera al estado de energía completa puede llevar más de 2 segundos.

Para minimizar la latencia, la máquina de estado usa un retraso para posponer la transición a estados de energía más baja. En la figura 1, se muestran los tiempos de AT&T para una radio 3G típica.


Figura 1: Máquina de estado de radio inalámbrica 3G convencional

La máquina de estado de radio en cada dispositivo (particularmente el retraso de transición asociado ["tiempo de cola"] y la latencia de inicio) variará según la tecnología de radio inalámbrica empleada (2G, 3G, LTE, etc.) y está definida y determinada por la red del operador a través de la cual opera el dispositivo.

En esta lección, se describe una máquina de estado representativa para una radio inalámbrica 3G típica basada en datos proporcionados por AT&T. No obstante, los principios generales y las prácticas recomendadas se aplican a todas las implementaciones de radio inalámbrica.

Este enfoque es muy eficaz para la navegación web típica, ya que evita la latencia no deseada mientras los usuarios navegan por la Web. El tiempo de cola relativamente bajo también garantiza que, una vez que haya finalizado la sesión de navegación, la radio podrá pasar a un estado de menor energía.

Lamentablemente, este enfoque puede conducir a apps ineficaces en los SO de smartphones modernos como Android, donde las apps se ejecutan tanto en primer plano (donde la latencia es importante) como en segundo plano (donde se debe priorizar la duración de la batería).

Qué tipo de impacto tienen las apps en la máquina de estado de radio

Cada vez que creas una nueva conexión de red, la radio pasa al estado de energía completa. En el caso de la típica máquina de estado de radio 3G que se describe arriba, permanecerá con energía completa mientras dure la transferencia, más 5 segundos adicionales de tiempo de cola, seguidos de 12 segundos en el estado de energía baja. Entonces, para un dispositivo 3G típico, cada sesión de transferencia de datos hará que la radio tome energía durante casi 20 segundos.

En la práctica, esto significa que una app que transfiere datos sin empaquetar durante 1 segundo cada 18 segundos mantendrá la radio inalámbrica siempre activa y la devolverá al estado de energía alta justo cuando esté a punto de entrar en modo de espera. En consecuencia, cada minuto consumirá batería en el estado de energía alta durante 18 segundos y en el estado de energía baja durante los 42 segundos restantes.

En comparación, la misma app que empaqueta transferencias de 3 segundos por minuto mantendrá la radio en estado de energía alta durante solo 8 segundos y en estado de energía baja durante solo 12 segundos más.

En el segundo ejemplo, se permite que la radio esté en espera durante 40 segundos adicionales cada minuto, lo que produce una reducción masiva en el consumo de la batería.


Figura 2: Uso de energía de radio inalámbrica relativa para transferencias empaquetadas y sin empaquetar

Carga previa de datos

La carga previa de datos es una forma eficaz de reducir el número de sesiones de transferencia de datos independientes. La carga previa permite descargar todos los datos que probablemente necesites durante un período determinado en un solo pico de actividad, a través de una única conexión, con capacidad completa.

Mediante la carga de las transferencias, se reduce la cantidad de activaciones de radio necesarias para descargar los datos. Como resultado, no solo se conserva la vida útil de la batería, sino que también se mejora la latencia, se reduce el ancho de banda requerido y se reducen los tiempos de descarga.

La carga previa también proporciona una experiencia del usuario mejorada, ya que minimiza la latencia en la app causada por esperar a que se completen las descargas antes de realizar una acción o mostrar datos.

Sin embargo, si se usa demasiado, la carga previa presenta el riesgo de aumentar el consumo de la batería y el ancho de banda, además de la cuota de descarga, ya que se descargan datos que no se usan. También es importante garantizar que la carga previa no retrase el inicio de la app mientras la aplicación espera a que se complete. En términos prácticos, puede significar que se procesen datos de forma progresiva o que se inicien transferencias consecutivas con prioridad, de modo que se descarguen y se procesen primero los datos necesarios para el inicio de la app.

La intensidad de la carga previa depende del tamaño de los datos que se descargan y de la probabilidad de que se usen. Como guía general, según la máquina de estado que se describe arriba, los datos que tienen un 50% de probabilidades de usarse en la sesión actual de usuario se pueden precargar durante aproximadamente 6 segundos (de 1 a 2 Mb) antes de que el costo potencial de la descarga de datos sin usar coincida con los ahorros potenciales si no se descargan los datos.

En términos generales, te recomendamos precargar los datos de modo que solo tengas que iniciar otra descarga cada 2 a 5 minutos y en el orden de 1 a 5 megabytes.

Según este principio, las descargas grandes, como los archivos de video, se deben realizar por partes a intervalos regulares (cada 2 a 5 minutos), y se deben precargar de manera eficaz solo los datos de video que posiblemente se vean en los próximos minutos.

Ten en cuenta que se deben empaquetar las descargas adicionales, como se describe en la siguiente sección, Transferencias y conexiones por lotes, y que estas aproximaciones variarán según el tipo y la velocidad de conexión, como se describe en Cómo modificar los patrones de descarga según el tipo de conectividad.

Estos son algunos ejemplos prácticos:

Reproductor de música

Puedes optar por precargar un álbum completo. Sin embargo, si el usuario deja de escuchar música después de la primera canción, se perderá una gran cantidad de ancho de banda y se reducirá la duración de la batería.

Un mejor enfoque sería mantener en el búfer una canción además de la que se esté reproduciendo. Para transmitir música, en lugar de mantener una transmisión continua que mantenga activa la radio en todo momento, procura usar HTTP Live Streaming para transmitir el audio en picos de actividad, simulando el enfoque de carga previa descrito anteriormente.

Lector de noticias

Muchas apps de noticias intentan reducir el ancho de banda descargando titulares solo después de que se ha seleccionado una categoría, artículos completos solo cuando el usuario quiere leerlos y miniaturas cuando se desplaza la vista.

Con este enfoque, la radio se verá obligada a permanecer activa durante la sesión de lectura de noticias de la mayoría de los usuarios a medida que se desplacen por los titulares, cambien las categorías y lean artículos. Como si esto fuera poco, el cambio constante entre estados de energía generará una latencia significativa cuando se cambie de categoría o se lean artículos.

Un mejor enfoque sería precargar una cantidad razonable de datos al inicio, comenzando con el primer conjunto de titulares y miniaturas de noticias, lo que asegura un tiempo de inicio de baja latencia, y continuando con los titulares y las miniaturas restantes, así como con el texto de cada artículo disponible, al menos, en la lista principal de titulares.

Otra alternativa es precargar todos los titulares; miniaturas; textos del artículo; y, posiblemente, imágenes completas del artículo (en general, en segundo plano y en un horario predeterminado). Con este enfoque, se corre el riesgo de dedicar una gran cantidad de ancho de banda y duración de la batería a la descarga de contenido que nunca se usará, por lo que debe implementarse con precaución.

Una solución es programar la descarga completa para que se produzca solo cuando haya conexión a Wi-Fi y, posiblemente, solo cuando el dispositivo se esté cargando. Esto se investiga más detalladamente en Cómo modificar los patrones de descarga según el tipo de conectividad.

Transferencias y conexiones por lotes

Cada vez que inicias una conexión, cualquiera que sea el tamaño de la transferencia de datos asociada, puedes hacer que la radio consuma energía durante aproximadamente 20 segundos usando una radio inalámbrica 3G típica.

Una app que haga ping en el servidor cada 20 segundos, solo para reconocer que la app se está ejecutando y es visible para el usuario, mantendrá la radio encendida indefinidamente, lo cual dará como resultado un costo de batería significativo y casi ninguna transferencia de datos real.

Si tenemos en cuenta esto, es importante empaquetar las transferencias de datos y crear una cola de transferencia pendiente. Si lo haces de forma correcta, puedes modificar con eficacia las transferencias de cambio de fase que se producen en una ventana de tiempo similar, para que todo suceda simultáneamente, y asegurar que la radio consuma energía durante el menor tiempo posible.

La filosofía subyacente de este enfoque es transferir la mayor cantidad de datos posible durante cada sesión de transferencia, con el objetivo de limitar la cantidad de sesiones que necesitas.

Esto significa que debes agrupar en lote tus transferencias colocando en cola las transferencias con tolerancia a retrasos y adelantar temporalmente las actualizaciones programadas y las cargas previas de modo que se ejecuten cuando se requieran transferencias urgentes. Del mismo modo, las actualizaciones programadas y la carga previa regular deben iniciar la ejecución de la cola de transferencias pendientes.

Para ver un ejemplo práctico, volvamos a los ejemplos anteriores de datos de carga previa.

Toma una aplicación de noticias que use la rutina de carga previa que se describe arriba. El lector de noticias recopila información analítica para comprender los patrones de lectura de sus usuarios y clasificar las historias más populares. Para mantener las noticias actualizadas, busca actualizaciones cada hora. Para conservar el ancho de banda, en lugar de descargar fotos completas para cada artículo, solo precarga miniaturas y descarga las fotos completas cuando se seleccionen.

En este ejemplo, hay que empaquetar toda la información analítica recopilada dentro de la app y colocarla en cola para descarga, en lugar de transmitirla a medida que se recopila. El paquete resultante se debe transferir cuando se descarga una foto en tamaño completo o cuando se realiza una actualización por hora.

Cualquier transferencia urgente u on demand, como la descarga de una imagen en tamaño completo debería interrumpir las actualizaciones programadas regularmente. Se debe ejecutar la actualización planificada al mismo tiempo que la transferencia on demand y se debe programar la próxima actualización después del intervalo establecido. Este enfoque disminuye el costo de realizar una actualización regular aprovechando la descarga necesaria de las fotografías más urgentes.

Reduce las conexiones

Suele ser más eficiente reutilizar las conexiones de red existentes que iniciar otras nuevas. La reutilización de las conexiones también permite que la red reaccione de manera más inteligente ante la congestión y los problemas de datos de red relacionados.

En lugar de crear múltiples conexiones simultáneas para descargar datos o encadenar múltiples solicitudes de GET consecutivas, siempre que sea posible, debes empaquetar esas solicitudes en un único GET.

Por ejemplo, en lugar de hacer múltiples consultas para varias categorías de noticias, sería más eficiente hacer una sola solicitud para que cada artículo de noticias se muestre en una única solicitud/respuesta. La radio inalámbrica se debe activar para transmitir los paquetes de rescisión/confirmación de rescisión asociados con el tiempo de espera del servidor y el cliente, por lo que también es una buena práctica cerrar las conexiones cuando no están en uso, en lugar de respetar estos tiempos de espera.

Dicho esto, cerrar una conexión demasiado pronto puede evitar que se reutilice, lo que luego requerirá una sobrecarga adicional para establecer una nueva conexión. Un método útil es no cerrar la conexión de inmediato, sino hacerlo antes de que caduque el tiempo de espera inherente.

Identifica problemas relacionados con el Generador de perfiles de red

Usa el Generador de perfiles de red para hacer un seguimiento de las solicitudes de red que realiza la aplicación. Puedes supervisar cómo y cuándo la app transfiere datos y optimiza el código subyacente de manera adecuada.

En la figura 3, se muestra un patrón de transferencia de pequeñas cantidades de datos con aproximadamente 15 segundos de diferencia, lo que sugiere que la eficacia podría mejorarse de forma considerable mediante la carga previa de cada solicitud o empaquetando las cargas.



Figura 3: Seguimiento del uso de red

Si supervisas la frecuencia de las transferencias de datos y la cantidad de datos transferidos durante cada conexión, podrás identificar las áreas de la aplicación en las que se puede hacer un uso más eficaz de la batería. Por lo general, buscarás picos cortos que se puedan retrasar o que causarán la interrupción de una transferencia posterior.

Para identificar mejor la causa de los picos de transferencia, la API de estadísticas de tráfico te permite etiquetar las transferencias de datos que ocurren dentro de un subproceso con el método TrafficStats.setThreadStatsTag(); luego, debes etiquetar y quitar las etiquetas de sockets individuales de forma manual con TrafficStats.tagSocket() y TrafficStats.untagSocket(). Por ejemplo:

Kotlin

TrafficStats.setThreadStatsTag(0xF00D)
TrafficStats.tagSocket(outputSocket)
// Transfer data using socket
TrafficStats.untagSocket(outputSocket)

Java

TrafficStats.setThreadStatsTag(0xF00D);
TrafficStats.tagSocket(outputSocket);
// Transfer data using socket
TrafficStats.untagSocket(outputSocket);

La biblioteca HttpURLConnection etiqueta los sockets automáticamente según el valor actual de TrafficStats.getThreadStatsTag(). La biblioteca también etiqueta y anula las etiquetas de sockets cuando se reciclan a través de grupos keep-alive.

Kotlin

class IdentifyTransferSpikeTask {
    @WorkerThread
    fun request(url: String) {
        TrafficStats.setThreadStatsTag(0xF00D)
        // Make network request using HttpURLConnection.connect()
        ...
        TrafficStats.clearThreadStatsTag();
    }
}

Java

public class IdentifyTransferSpikeTask {
    @WorkerThread
    public void request(String url) {
        TrafficStats.setThreadStatsTag(0xF00D);
        // Make network request using HttpURLConnection.connect()
        ...
        TrafficStats.clearThreadStatsTag();
    }
}

El etiquetado de sockets es compatible con Android 4.0, pero las estadísticas en tiempo real solo se mostrarán en dispositivos con Android 4.0.3 o versiones posteriores.