La optimización de la memoria es fundamental para garantizar un rendimiento fluido, evitar fallas de la app y mantener la estabilidad del sistema y el estado de la plataforma. Si bien el uso de la memoria debe supervisarse y optimizarse en todas las apps, las apps de contenido para TV tienen desafíos específicos que difieren de las apps típicas de Android para dispositivos de mano.
El consumo elevado de memoria puede provocar problemas con los comportamientos de la app y del sistema, como los siguientes:
- La app en sí puede volverse lenta o con retrasos, o en el peor de los casos, finalizarse.
- Los servicios del sistema visibles para el usuario (Control de volumen, panel de configuración de imagen, Asistente de voz, etcétera) tienen mucha latencia o pueden no funcionar en absoluto.
- Los componentes del sistema pueden finalizarse y, luego, reiniciarse, lo que activa picos de contención de recursos extremos y afecta directamente a la app en primer plano.
- La transición al selector puede retrasarse significativamente y hacer que la app en primer plano no responda hasta que finalice la transición.
- El sistema puede entrar en una situación de recuperación directa, lo que detendrá temporalmente la ejecución de un subproceso mientras espera la asignación de memoria. Esto puede sucederle a cualquier subproceso, como el principal o los relacionados con el códec, lo que podría provocar fallas de audio y video, y fallas de la IU.
Consideraciones sobre la memoria en dispositivos de TV
Los dispositivos de TV suelen tener mucha menos memoria que los teléfonos o las tablets. Por ejemplo, una configuración que podemos ver en la TV es 1 GB de RAM y resolución de video de 1080p. Al mismo tiempo, la mayoría de las apps para TV tienen funciones similares, por lo que la implementación es similar y los desafíos son comunes. Estas dos situaciones presentan problemas que no se ven en otros tipos de dispositivos y apps:
- Por lo general, las apps de TV de contenido multimedia se componen de vistas de imágenes en cuadrícula y imágenes de fondo de pantalla completa, que requieren cargar muchas imágenes en la memoria en un período breve.
- Las apps para TV reproducen transmisiones multimedia que requieren asignar 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 (saltos, cambio de episodio, cambio de pista de audio, etc.) pueden ejercer presión adicional sobre la memoria si no se implementan correctamente.
Información sobre los dispositivos de TV
Esta guía se enfoca principalmente en el uso de la memoria de la app 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 instalado el dispositivo.
- Resolución de la IU del dispositivo: Es la resolución que usa el dispositivo para renderizar la IU del SO y 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 a la que el dispositivo puede reproducir videos.
Esto lleva a categorizar los 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 | 2160p | 1080p | No* |
Dispositivos de TV con poca RAM
Estos dispositivos se encuentran en una situación de restricción de memoria y mostrarán que ActivityManager.isLowRAMDevice()
es 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 × 720), resolución de video de 1080p/FullHD (1920 × 1080)
- Dispositivos de 1.5 GB: 1.5 GB de RAM, resolución de 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 la memoria en estos dispositivos, ya que un uso inadecuado específico de la memoria 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 que supervises cada sección de la memoria con el Generador de perfiles de memoria de Android Studio. Las apps para TVs deben perfilar su uso de memoria y trabajar para que sus categorías estén 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 registradas. Para la definición de umbrales de las apps para TV, nos enfocaremos en tres categorías de memoria:
- Anónima + Intercambio: Se compone de Java + memoria de asignación nativa y de pila en Android Studio.
- Gráficos: Se informan directamente en la herramienta del generador 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 | Propósito | Objetivos de uso (1 GB) |
---|---|---|
Anónimo + intercambio (Java + nativo + pila) | Se usa para asignaciones, búferes de medios, variables y otras tareas que requieren mucha memoria. | Menor que 160 MB |
Gráficos | La GPU los usa para las texturas y los búferes relacionados con la visualización. | 30-40 MB |
Archivo | Se usa para las páginas de código y los archivos en la memoria. | 60-80 MB |
La memoria total máxima (Anon+Swap + Graphics + File) no debe superar lo siguiente:
- 280 MB de uso de memoria total (Anon+Swap + Graphics + File) para dispositivos con 1 GB de RAM.
Se recomienda no exceder los siguientes valores:
- 200 MB de uso de memoria en (Anon+Swap + Graphics).
Memoria de archivos
Como orientación general para la memoria con copia de seguridad en archivos, ten en cuenta lo siguiente:
- En general, la administración de memoria del SO controla bien la memoria de los archivos.
- En este momento, no hemos descubierto 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 no utilizadas en tu compilación y usa subconjuntos pequeños de bibliotecas en lugar de las completas cuando sea posible.
- No mantengas archivos grandes abiertos en la memoria y libéralos en cuanto termines de usarlos.
- Para reducir el tamaño del código compilado de las clases 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 la memoria en dispositivos de TV.
Memoria de gráficos
Usa los formatos y las 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 deben reducirse a 720p en un dispositivo de IU de 720p.
- Usa mapas de bits con copia de seguridad de hardware siempre que sea posible.
- En bibliotecas como Glide, habilita la función
Downsampler.ALLOW_HARDWARE_CONFIG
, que está inhabilitada de forma predeterminada. Si habilitas esta opción, se evita duplicar los mapas de bits que, de otro modo, estarían en la memoria gráfica y en la memoria anónima.
- En bibliotecas como Glide, habilita la función
- Evita las renderizaciones intermedias y las renderizaciones repetidas
- 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 las forman. Por lo general, se trata de una llamada “renderización intermedia”.
- En el caso de las aplicaciones del SDK de Android, a menudo puedes quitarlas con la marca de diseño
forceHasOverlappedRendering:false
para inhabilitar las renderizaciones intermedias de este diseño. - Consulta Evita las renderizaciones superpuestas para obtener información sobre las renderizaciones superpuestas.
- Evita cargar imágenes de marcador de posición cuando sea posible. Usa
@android:color/
o@color
para las texturas de marcador de posición. - Evita combinar varias imágenes en el dispositivo cuando la composición se pueda realizar sin conexión. Prefiere cargar imágenes independientes en lugar de realizar la composición de imágenes a partir de imágenes descargadas.
- Sigue la guía Cómo administrar mapas de bits para controlar mejor los mapas de bits.
Memoria anónima y de intercambio
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 comprobar si el dispositivo tiene limitaciones de memoria y adaptarte a esta situación siguiendo estos lineamientos.
- Medios:
- Especifica un tamaño variable para los búferes de contenido multimedia 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-60 MB para 1 GB / 1080p
- Entre 60 y 80 MB para 1.5 GB / 1080p
- 80-100 MB para 1.5 GB / 2160p
- Entre 100 y 120 MB para 2 GB / 2160p
- Asignaciones de memoria multimedia libre cuando se cambia un episodio para evitar aumentos en la cantidad total de memoria anónima.
- Libera y detén los recursos multimedia de inmediato cuando se detenga la app: Usa las devoluciones de llamada de ciclo de vida de la actividad para controlar los recursos de audio y video. Si no es una app de audio, detén la reproducción cuando se produzca
onStop()
en tus actividades, guarda todo el trabajo que estés realizando y establece que se liberen los recursos. Para programar trabajo que podrías necesitar más adelante. Consulta la sección Jobs y alarmas.- Puedes usar componentes que tienen en cuenta el ciclo de vida, como
LiveData
yLifecycleOwner
, para ayudarte a controlar las llamadas al ciclo de vida de la actividad. - Para que tu trabajo sea consciente del ciclo de vida, también puedes usar corrutinas de Kotlin y flujos de Kotlin.
- Puedes usar componentes que tienen en cuenta el ciclo de vida, como
- Presta atención a la memoria del búfer cuando saltes de un video a otro: Los desarrolladores suelen asignar entre 15 y 60 segundos adicionales de contenido futuro cuando buscan tener el video listo para el usuario, pero esto crea una sobrecarga de memoria adicional.
En general, no tomes más de 5 segundos de búfer futuro hasta que el usuario seleccione la nueva posición del video. Si necesitas almacenar en búfer previamente un tiempo adicional durante el salto, asegúrate de lo siguiente:
- Asignar el búfer de salto con anticipación y reutilizarlo
- El tamaño del búfer no debe ser superior a 15-25 MB (según la memoria del dispositivo).
- Especifica un tamaño variable para los búferes de contenido multimedia 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 gráfica para asegurarte de no duplicar imágenes en la memoria anónima.
- Las imágenes suelen ser los mayores usuarios de memoria, por lo que duplicarlas puede ejercer mucha presión sobre el dispositivo. Esto es especialmente cierto durante la navegación intensa de vistas de cuadrícula de imágenes.
- Libera las asignaciones descartando sus referencias cuando muevas pantallas: Asegúrate de que no queden referencias a objetos y mapas de bits.
- Usa la guía de memoria gráfica para asegurarte de no duplicar imágenes en la memoria anónima.
- Bibliotecas:
- Perfila las asignaciones de memoria de las bibliotecas cuando agregues bibliotecas nuevas, ya que también pueden cargar bibliotecas adicionales, lo que también puede realizar asignaciones y crear vinculaciones.
- Herramientas de 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 crean una sobrecarga de memoria adicional durante el inicio, en el que la carga de la app limita la memoria. Primero, muestra una pantalla de carga o de presentación y realiza solicitudes de red una vez que la IU esté en su lugar.
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 lo usas. Asegúrate de liberar la vinculación en cuanto ya no sea necesaria.
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 PlayIntegrity
StandardIntegrityManager
antes de reproducir 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 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 el servicio se desconectó en caso de que se deba volver a realizar algún trabajo de facturación y, luego, vuelve a ejecutarBillingClient.endConnection()
cuando termines.
- GMS FontsProvider
- Se prefiere usar fuentes independientes en dispositivos con poca RAM en lugar de usar el proveedor de fuentes, ya que descargar las fuentes es costoso 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 función de texto a voz de Gboard o la biblioteca androidx.leanback.
- Sigue los lineamientos de Búsqueda para implementar la búsqueda.
- Nota: leanback dejó de estar disponible y las apps deben migrar a TV Compose.
- Para 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 de tu app.
- Para apps de Leanback: Usa la función de texto a voz de Gboard o la biblioteca 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. Incluso si los servicios en primer plano son útiles porque se pueden mantener en ejecución mientras la aplicación está en segundo plano, las apps para TV deben seguir estos lineamientos:
En Android TV y Google TV, solo se permite que los servicios en primer plano sigan ejecutándose una vez que el usuario salga de la app:
- En el caso de las apps de audio: Solo se permite que los servicios en primer plano sigan ejecutándose una vez que el usuario salga 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 abandona la app, ya que no hay una notificación para informarle al usuario que la app sigue ejecutándose y consumiendo recursos.
- Para las tareas en segundo plano, como actualizar las recomendaciones o Ver a continuación, usa
WorkManager
.
Trabajos y alarmas
WorkManager
es la API de Android de última generación para programar tareas recurrentes en segundo plano.
WorkManager usará el nuevo JobScheduler
cuando esté disponible (SDK 23 y versiones posteriores) y el AlarmManager
anterior cuando no lo esté. Para conocer las prácticas recomendadas para realizar tareas programadas en la TV, sigue estas recomendaciones:
- Evita usar las APIs de
AlarmManager
en 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 las tareas (por ejemplo, cuando el dispositivo está inactivo). - En dispositivos con poca RAM, evita ejecutar tareas, a menos que sea estrictamente necesario. Si es necesario, usa WorkManager
WorkRequest
solo para actualizar las recomendaciones después de la reproducción y trata de hacerlo mientras la app esté abierta. - Define WorkManager
Constraints
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 tareas con regularidad (por ejemplo, para actualizar Mirar a continuación en función de la actividad de reproducción de contenido de un usuario en tu app en otro dispositivo), mantén el uso de la memoria bajo y el consumo de memoria de la tarea por debajo de 30 MB.
Otros lineamientos generales
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 de inmediato los
objetos que no se usen.
- No mantengas referencias a objetos, en especial a mapas de bits.
- Evita usar
System.gc()
y las llamadas de memoria de liberación directa, ya que interfieren con el proceso de manejo de memoria del sistema. Por ejemplo, en 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 oRecyclerView
en el kit de herramientas de la IU de Leanback, ahora obsoleto, para volver a usar 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 probable que no cambien y define intervalos de actualización que eviten asignar memoria externa adicional.
- Verifica si hay posibles fugas de memoria.
- Ten cuidado con los casos típicos de fuga de memoria, como referencias dentro de subprocesos anónimos, reasignación de búferes de video que nunca se liberan y otras situaciones similares.
- Usa el volcado de montón para depurar las 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.
Resumen de herramientas
- Usa la herramienta del Generador de perfiles de memoria de Android Studio para verificar el consumo de memoria durante el uso.
- Usa heapdump para verificar asignaciones específicas de objetos y mapas de bits.
- Usa el generador de perfiles de memoria nativo 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.