La optimización de la memoria es fundamental para garantizar un rendimiento fluido, evitar fallas en las apps y mantener la estabilidad del sistema y el buen estado de la plataforma. Si bien el uso de memoria debe supervisarse y optimizarse en todas las apps, las apps de contenido para dispositivos de TV tienen desafíos específicos que difieren de las apps típicas para dispositivos portátiles de Android.
El consumo elevado de memoria puede provocar problemas con el comportamiento de la app y el sistema, incluidos los siguientes:
- La app en sí puede volverse lenta o rezagada, o, en el peor de los casos, cerrarse.
- Los servicios del sistema visibles para el usuario (control de volumen, panel de configuración de imagen, Asistente de voz, etcétera) se vuelven muy lentos o pueden no funcionar en absoluto.
- El proceso daemon del optimizador de poca memoria (LMK) puede reaccionar a la alta presión de la memoria finalizando los procesos menos esenciales. Luego, estos componentes pueden reiniciarse poco después, lo que genera picos de mayor contención de recursos que pueden afectar directamente la app en primer plano.
- La transición al Selector puede demorarse significativamente y hacer que la app en primer plano parezca no responder hasta que finalice la transición.
- El sistema puede comenzar a usar la recuperación directa, y pausar temporalmente la ejecución de un subproceso mientras espera la asignación de memoria. Esto puede ocurrir en cualquier subproceso, como el subproceso principal o los subprocesos relacionados con el códec, lo que puede provocar pérdidas de fotogramas de audio y video, y fallas en la IU.
Consideraciones sobre la memoria en dispositivos de TV
Por lo general, los dispositivos de TV tienen mucha menos memoria que los teléfonos o las tablets. Por ejemplo, una configuración que podemos ver en la TV es de 1 GB de RAM y una resolución de video de 1080p. Al mismo tiempo, la mayoría de las apps para TVs tienen funciones similares, por lo que la implementación y los desafíos son similares. Estas dos situaciones presentan problemas que no se ven en otros tipos de dispositivos y apps:
- Las apps de TV de contenido multimedia suelen estar compuestas por vistas de imágenes en cuadrícula y imágenes de fondo en pantalla completa, lo que requiere cargar muchas imágenes en la memoria en un corto período.
- Las apps para TVs reproducen transmisiones multimedia que requieren que se asigne una cierta cantidad de memoria para reproducir video y audio, y necesitan búferes multimedia considerables para garantizar una reproducción fluida.
- Las funciones multimedia adicionales (búsqueda, cambio de episodio, cambio de pista de audio, etcétera) pueden ejercer presión adicional sobre la memoria si no se implementan correctamente.
Información sobre los dispositivos para TV
En esta guía, se analiza principalmente el uso de memoria de las apps y los objetivos de memoria para dispositivos con poca RAM.
En los dispositivos de TV, ten en cuenta estas características:
- Memoria del dispositivo: Es la cantidad de memoria de acceso aleatorio (RAM) que tiene instalada el dispositivo.
- Resolución de la IU del dispositivo: Es la resolución que usa el dispositivo para renderizar la IU del SO y de las aplicaciones. Por lo general, es inferior a la resolución de video del dispositivo.
- Resolución de video: Es la resolución máxima con la que el dispositivo puede reproducir videos.
Esto lleva a categorizar diferentes tipos de dispositivos y cómo deben usar la memoria.
Resumen de dispositivos de TV
| Memoria del dispositivo | Resolución de video del dispositivo | Resolución de la IU del dispositivo | isLowRAMDevice() |
|---|---|---|---|
| 1 GB | 1080p | 720p | Sí |
| 1.5 GB | 2160p | 1080p | Sí |
| ≥1.5 GB | 1080p | 720p o 1080p | No* |
| 2 GB o más | 2160p | 1080p | No* |
Dispositivos de TV con poca RAM
Estos dispositivos se encuentran en una situación de memoria limitada y mostrarán ActivityManager.isLowRAMDevice() como verdadero. Las aplicaciones que se ejecutan en dispositivos de TV con poca RAM deben implementar medidas adicionales de control de memoria.
Consideramos que los dispositivos con las siguientes características pertenecen a esta categoría:
- Dispositivos de 1 GB: 1 GB de RAM, resolución de la IU de 720p/HD (1280 x 720), resolución de video de 1080p/FullHD (1920 x 1080)
- Dispositivos con 1.5 GB: 1.5 GB de RAM, resolución de la IU de 1080p/FullHD (1920 x 1080), resolución de video de 2160p/UltraHD/4K (3840 x 2160)
- Otras situaciones en las que el OEM definió la marca
ActivityManager.isLowRAMDevice()debido a restricciones de memoria adicionales
Dispositivos de TV normales
Estos dispositivos no sufren una situación de presión de memoria tan significativa. Consideramos que estos dispositivos tienen las siguientes características:
- ≥1.5 GB de RAM, IU de 720p o 1080p y resolución de video de 1080p
- ≥2 GB de RAM, IU de 1080p y resolución de video de 1080p o 2160p
Esto no significa que las apps no deban preocuparse por el uso de memoria en estos dispositivos, ya que el uso inadecuado de memoria específico aún puede agotar la memoria disponible y tener un rendimiento deficiente.
Objetivos de memoria en dispositivos de TV con poca RAM
Cuando midas la memoria en estos dispositivos, te recomendamos enfáticamente que supervises cada sección de la memoria con el generador de perfiles de memoria de Android Studio. Las apps para TVs deben generar perfiles de su uso de memoria y trabajar para que sus categorías se encuentren por debajo de los umbrales que definimos en esta sección.

En la sección Cómo se cuenta la memoria, encontrarás una explicación detallada de las cifras de memoria informadas. Para definir los umbrales de las apps para TVs, nos centraremos en tres categorías de memoria:
- Anónimo + Swap: Se compone de memoria de asignación de pila nativa y de Java en Android Studio.
- Gráficos: Se informan directamente en la herramienta de generación de perfiles. Por lo general, se compone de texturas gráficas.
- Archivo: Se informa como las categorías "Código" y "Otros" en Android Studio.
Con estas definiciones, la siguiente tabla indica el valor máximo que debe usar cada tipo de grupo de memoria:
| Tipo de memoria | Purpose | Objetivos de uso (1 GB) |
|---|---|---|
| Anónimo + Swap (Java + Nativo + Pila) | Se usa para asignaciones, búferes de medios, variables y otras tareas que requieren mucha memoria. | < 160 MB |
| Gráficos | La GPU lo usa para texturas y búferes relacionados con la pantalla. | 30 a 40 MB |
| Archivo | Se usa para páginas de códigos y archivos en la memoria. | 60 a 80 MB |
La memoria total máxima (anónima + intercambio + gráficos + archivo) no debe superar lo siguiente:
- 280 MB de uso de memoria total (Anón. + Swap + Gráficos + Archivo) para dispositivos con 1 GB de RAM baja
Se recomienda no exceder los siguientes límites:
- 200 MB de uso de memoria en (Anónimo + Swap + Gráficos).
Memoria de archivos
Como orientación general para la memoria respaldada por archivos, ten en cuenta lo siguiente:
- En general, la administración de memoria del SO maneja bien la memoria de archivos.
- En este momento, no consideramos que sea una causa importante de presión de memoria.
Sin embargo, cuando se trata de la memoria de archivos en general, ten en cuenta lo siguiente:
- No incluyas bibliotecas sin usar en tu compilación y, cuando sea posible, usa pequeños subconjuntos de bibliotecas en lugar de las completas.
- No mantengas abiertos archivos grandes en la memoria y libéralos en cuanto termines de usarlos.
- Minimiza el tamaño del código compilado para las clases de Java y Kotlin. Consulta la guía Cómo reducir, ofuscar y optimizar tu app.
Recomendaciones de TV específicas
En esta sección, se proporcionan recomendaciones específicas para optimizar el uso de memoria en dispositivos de TV.
Memoria de gráficos
Usa formatos y resoluciones de imagen adecuados.
- No cargues imágenes con una resolución superior a la de la IU del dispositivo. Por ejemplo, las imágenes de 1080p se deben reducir a 720p en un dispositivo de IU de 720p.
- Usa mapas de bits respaldados por hardware cuando sea posible.
- En bibliotecas como Glide, habilita la función
Downsampler.ALLOW_HARDWARE_CONFIG, que está inhabilitada de forma predeterminada. Habilitar esta opción evita duplicar mapas de bits que, de lo contrario, estarían tanto en la memoria de gráficos como en la memoria anónima.
- En bibliotecas como Glide, habilita la función
- Evita las renderizaciones intermedias y las nuevas renderizaciones.
- Estos se pueden identificar con el Inspector de GPU de Android:
- En la sección "Texturas", busca imágenes que sean pasos hacia la renderización final en lugar de ser solo los elementos que la forman. Esto se conoce comúnmente como "renderización intermedia".
- En el caso de las aplicaciones del SDK de Android, a menudo puedes quitar estos elementos con la marca de diseño
forceHasOverlappedRendering:falsepara inhabilitar las renderizaciones intermedias de este diseño. - Consulta Cómo evitar la superposición de renderizaciones para obtener un excelente recurso sobre la superposición de renderizaciones.
- Evita cargar imágenes de marcador de posición cuando sea posible. Usa
@android:color/o@colorpara las texturas de marcador de posición. - Evita componer varias imágenes en el dispositivo cuando la composición se pueda realizar sin conexión. Prefiere cargar imágenes independientes en lugar de componer imágenes a partir de imágenes descargadas.
- Sigue la guía de cómo administrar mapas de bits para trabajar mejor con mapas de bits.
Memoria de Anon+Swap
Anon+Swap se compone de asignaciones nativas, de Java y de pila en el generador de perfiles de memoria de Android Studio. Usa ActivityManager.isLowMemoryDevice() para verificar si el dispositivo tiene limitaciones de memoria y adáptate a esta situación siguiendo estos lineamientos.
- Medios:
- Especifica un tamaño variable para los búferes de medios según la RAM del dispositivo y la resolución de reproducción de video. Esto debería representar 1 minuto de reproducción de video:
- 40 a 60 MB por 1 GB / 1080p
- 60 a 80 MB para 1.5 GB / 1080p
- De 80 a 100 MB para 1.5 GB / 2160p
- 100 a 120 MB para 2 GB / 2160p
- Liberar asignaciones de memoria de medios cuando se cambia de episodio para evitar aumentos en la cantidad total de memoria anónima
- Libera y detén los recursos multimedia de inmediato cuando se detenga tu app: Usa las devoluciones de llamada del ciclo de vida de la actividad para controlar los recursos de audio y video. Si no tienes una app de audio, detén la reproducción cuando se produzca un evento
onStop()en tus actividades, guarda todo el trabajo que estés realizando y configura tus recursos para que se liberen. Para programar trabajos que tal vez necesites más adelante Consulta la sección Trabajos y alarmas.- Puedes usar componentes que prioricen el ciclo de vida, como
LiveDatayLifecycleOwner, para ayudarte a controlar las llamadas del ciclo de vida de la actividad. - Para que tu trabajo sea compatible con el ciclo de vida, también puedes usar corrutinas de Kotlin y flujos de Kotlin.
- Puedes usar componentes que prioricen el ciclo de vida, como
- Presta atención a la memoria del búfer cuando busques videos: Los desarrolladores suelen asignar entre 15 y 60 segundos adicionales de contenido futuro cuando buscan videos para que estén listos para el usuario, pero esto genera una sobrecarga de memoria adicional.
En general, no tomes más de 5 s de búfer futuro hasta que el usuario haya seleccionado la nueva posición del video. Si necesitas almacenar en búfer tiempo adicional durante la búsqueda, asegúrate de hacer lo siguiente:
- Asigna el búfer de búsqueda con anticipación y reutilízalo.
- El tamaño del búfer no debe ser superior a 15 a 25 MB (según la memoria del dispositivo).
- Especifica un tamaño variable para los búferes de medios según la RAM del dispositivo y la resolución de reproducción de video. Esto debería representar 1 minuto de reproducción de video:
- Asignaciones:
- Usa la guía de memoria de gráficos para asegurarte de no duplicar imágenes en la memoria anónima.
- Las imágenes suelen ser el mayor consumidor de memoria, por lo que su duplicación puede ejercer mucha presión sobre el dispositivo. Esto es especialmente cierto durante la navegación intensa por las vistas de cuadrícula de imágenes.
- Libera las asignaciones descartando sus referencias cuando cambies de pantalla: Asegúrate de que no queden referencias a mapas de bits ni objetos.
- Usa la guía de memoria de gráficos para asegurarte de no duplicar imágenes en la memoria anónima.
- Bibliotecas:
- Asigna memoria de perfil desde las bibliotecas cuando agregues bibliotecas nuevas, ya que es posible que también carguen bibliotecas adicionales, lo que también puede generar asignaciones y crear enlaces.
- Redes:
- No realices llamadas de red de bloqueo durante el inicio de la app, ya que ralentizan el tiempo de inicio de la aplicación y generan una sobrecarga de memoria adicional en el lanzamiento, en el que la memoria se ve particularmente limitada por la carga de la app. Primero, muestra una pantalla de carga o de presentación y realiza solicitudes de red una vez que la IU esté lista.
Vinculaciones
Las vinculaciones introducen una sobrecarga de memoria adicional, ya que traen otras aplicaciones a la memoria o aumentan el consumo de memoria de la app vinculada (si ya está en la memoria) para facilitar la llamada a la API. Como resultado, se reduce la memoria disponible para la app en primer plano. Cuando vincules un servicio, ten en cuenta cuándo y durante cuánto tiempo usas la vinculación. Asegúrate de liberar la vinculación tan pronto como ya no la necesites.
Vinculaciones típicas y prácticas recomendadas:
- API de Play Integrity: Se usa para verificar la integridad del dispositivo.
- Verifica la integridad del dispositivo después de la pantalla de carga y antes de la reproducción de contenido multimedia.
- Libera referencias a Play Integrity
StandardIntegrityManagerantes de reproducir el contenido.
- Biblioteca de Facturación Play: Se usa para administrar suscripciones y compras con Google Play
- Inicializa la biblioteca después de la pantalla de carga y controla todo el trabajo de facturación antes de reproducir cualquier contenido multimedia.
- Usa
BillingClient.endConnection()cuando termines de usar la biblioteca y siempre antes de reproducir videos o contenido multimedia. - Usa
BillingClient.isReady()yBillingClient.getConnectionState()para verificar si se desconectó el servicio en caso de que se deba volver a realizar algún trabajo de facturación y, luego, vuelve a usarBillingClient.endConnection()después de terminar.
- GMS FontsProvider
- En dispositivos con poca RAM, es preferible usar fuentes independientes en lugar de un proveedor de fuentes, ya que la descarga de fuentes es costosa y FontsProvider vinculará servicios para hacerlo.
- Biblioteca de Asistente de Google: A veces se usa para la búsqueda y la búsqueda en la app. Si es posible, reemplaza esta biblioteca.
- Para apps de Leanback: Usa la biblioteca de texto a voz de Gboard o androidx.leanback.
- Sigue los lineamientos de la Búsqueda para implementar la búsqueda.
- Nota: Leanback dejó de estar disponible, y las apps deben migrar a TV Compose.
- Para las apps de Compose:
- Usa la función de texto a voz de Gboard para implementar la búsqueda por voz.
- Implementa Ver a continuación para que se pueda descubrir el contenido multimedia en tu app.
- Para apps de Leanback: Usa la biblioteca de texto a voz de Gboard o androidx.leanback.
Servicios en primer plano
Los servicios en primer plano son un tipo especial de servicio que está vinculado a una notificación. Esta notificación se muestra en la bandeja de notificaciones de teléfonos y tablets, pero los dispositivos de TV no tienen una bandeja de notificaciones en el mismo sentido que esos dispositivos. Aunque los servicios en primer plano son útiles porque pueden seguir ejecutándose mientras la aplicación está en segundo plano, las apps para TVs deben seguir estos lineamientos:
En Android TV y Google TV, los servicios en primer plano solo pueden seguir ejecutándose una vez que el usuario sale de la app en los siguientes casos:
- En el caso de las apps de audio: Los servicios en primer plano solo pueden seguir ejecutándose una vez que el usuario sale de la app para seguir reproduciendo la pista de audio. El servicio debe detenerse inmediatamente después de que finalice la reproducción de audio.
- Para cualquier otra app: todos los servicios en primer plano deben detenerse una vez que el usuario salga de la app, ya que no hay ninguna notificación para informarle que la app sigue en ejecución y consumiendo recursos.
- Para los trabajos en segundo plano, como la actualización de recomendaciones o Ver a continuación, usa
WorkManager.
Trabajos y alarmas
WorkManager es la API de Android más avanzada para programar trabajos recurrentes en segundo plano.
WorkManager usará el nuevo JobScheduler cuando esté disponible (SDK 23 y versiones posteriores) y el antiguo AlarmManager cuando no lo esté. Para seguir las prácticas recomendadas cuando realices trabajos programados en la TV, sigue estas recomendaciones:
- Evita usar las APIs de
AlarmManageren el SDK 23 y versiones posteriores, en especialAlarmManager.set(),AlarmManager.setExact()y métodos similares, ya que no permiten que el sistema decida el momento adecuado para ejecutar los trabajos (por ejemplo, cuando el dispositivo está inactivo). - En dispositivos con poca RAM, evita ejecutar trabajos a menos que sea estrictamente necesario. Si es necesario, usa WorkManager
WorkRequestsolo para actualizar las recomendaciones después de la reproducción y trata de hacerlo mientras la app aún esté abierta. Define
Constraintsde WorkManager para permitir que el sistema ejecute tus trabajos cuando sea el momento adecuado:
Kotlin
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()
Java
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()
- Si debes ejecutar trabajos con regularidad (por ejemplo, para actualizar Próximo video según la actividad de visualización de contenido de un usuario en tu app en otro dispositivo), mantén bajo el uso de memoria y el consumo de memoria del trabajo por debajo de los 30 MB.
Consideraciones generales sobre la memoria
Los siguientes lineamientos proporcionan información general sobre el desarrollo de apps para Android:
- Minimiza las asignaciones de objetos, optimiza la reutilización de objetos y libera los objetos no utilizados de inmediato.
- No mantengas referencias a objetos, en especial a mapas de bits.
- Evita usar
System.gc()y llamadas directas para liberar memoria, ya que interfieren en el proceso de administración de memoria del sistema. Por ejemplo, en los dispositivos que usan zRAM, una llamada forzada agc()puede aumentar temporalmente el uso de memoria debido a la compresión y descompresión de la memoria. - Usa
LazyList, como se muestra en un navegador de catálogos en Compose oRecyclerViewen el kit de herramientas de IU de Leanback, que ahora está en desuso, para reutilizar vistas y no volver a crear elementos de lista. - Almacena en caché de forma local los elementos que se leen de proveedores de contenido externos que es poco probable que cambien y define intervalos de actualización que eviten asignar memoria externa adicional.
- Verifica si hay posibles pérdidas de memoria.
- Ten cuidado con los casos típicos de fugas de memoria, como las referencias dentro de subprocesos anónimos, la reasignación de búferes de video que nunca se liberan y otras situaciones similares.
- Usa volcado de montón para depurar fugas de memoria.
- Genera perfiles de Baseline para minimizar la cantidad de compilación justo a tiempo que se necesita cuando se ejecuta tu app en un inicio en frío.
Información sobre la recuperación directa de memoria
Cuando una aplicación para Android TV solicita memoria y el sistema está bajo presión, es posible que el kernel de Linux, que sustenta Android, tenga que usar la recuperación directa de memoria.
El proceso implica pausar por completo cualquier subproceso de asignación para esperar a que se liberen páginas de memoria. Esto ocurre cuando la recuperación en segundo plano no puede mantener de forma proactiva un grupo suficiente de memoria.
Esto puede provocar pausas o tirones notables en la experiencia del usuario, ya que el sistema detiene la asignación de subprocesos hasta que haya suficiente memoria disponible. En este sentido, la asignación de subprocesos no se limita a las llamadas de código de la aplicación, como malloc(); por ejemplo, se debe asignar memoria a la página en las páginas de código.
Resumen de herramientas
- Usa la herramienta Generador de perfiles de memoria de Android Studio para verificar el consumo de memoria durante el uso.
- Usa heapdump para verificar las asignaciones específicas de objetos y mapas de bits.
- Usa el generador de perfiles de memoria nativa para verificar las asignaciones que no son de Java o Kotlin.
- Usa el Inspector de GPU de Android para verificar las asignaciones de gráficos.