La memoria de acceso aleatorio (RAM) representa un recurso valioso en cualquier entorno de desarrollo de software, pero adquiere una importancia crítica en los sistemas operativos móviles donde la memoria física suele estar restringida. Aunque el Android Runtime (ART) y la máquina virtual Dalvik ejecutan recolección automática de basura, esto no significa que pueda ignorar cuándo y dónde asignar y liberar memoria en su aplicación. Continúa siendo necesario evitar fugas de memoria y liberar todas las referencias a objetos definidas por los callbacks del ciclo de vida.
Esta guía detalla las estrategias para reducir proactivamente el consumo de memoria de su aplicación. Para información sobre cómo el sistema operativo Android gestiona la memoria, consulte la documentación oficial de gestión de memoria de Android.
Monitoreo del Uso de Memoria
Antes de resolver problemas de consumo de memoria, debe identificralos. El Memory Profiler de Android Studio permite detectar y diagnosticar problemas de memoria mediante:
- Visualización de la asignación de memoria a lo largo del tiempo, mostrando la cantidad de memoria utilizada, el número de objetos Java generados y los momentos de recolección de basura.
- Iniciación de eventos de recolección de basura y captura deinstantáneas del heap de Java durante la ejecución.
- Registro de asignaciones de memoria para examinar cada objeto分配, ver rastros de pila y navegar al código correspondiente en Android Studio.
Liberación de Memoria Mediante Callbacks del Sistema
Como se describe en la documentación de gestión de memoria de Android, el sistema recupera memoria de las aplicaciones de múltiples formas, e incluso puede terminate procesos completos si es necesario para tareas críticas. Para equilibrar la memoria del sistema y evitar la terminación de su proceso, implemente la interfaz ComponentCallbacks2 en su Activity. El método callback onTrimMemory() permite detectar eventos relacionados con memoria tanto en primer plano como en segundo plano, liberando objetos en respuesta a eventos del ciclo de vida o del sistema.
A continuación se muestra una implementación de ejemplo:
1 import android.content.ComponentCallbacks2;
2
3 public class MainActivity extends AppCompatActivity
4 implements ComponentCallbacks2 {
5
6 private static final String TAG = "MainActivity";
7
8 @Override
9 public void onTrimMemory(int nivel) {
10
11 if (nivel == TRIM_MEMORY_UI_HIDDEN) {
12 // La interfaz de usuario ha desaparecido
13 // Liberar recursos asociados a la vista
14 }
15
16 if (nivel >= TRIM_MEMORY_RUNNING_LOW &&
17 nivel <= TRIM_MEMORY_RUNNING_CRITICAL) {
18 // El dispositivo tiene memoria limitada
19 // Liberar recursos no esenciales para funcionamiento
20 }
21
22 if (nivel >= TRIM_MEMORY_BACKGROUND &&
23 nivel <= TRIM_MEMORY_COMPLETE) {
24 // La app está en la lista LRU
25 // Liberar tanta memoria como sea posible
26 }
27 }
28 }
Este método está disponible desde Android 4.0 (API nivel 14). Para versiones anteriores, puede utilizar onLowMemory(), equivalente近似amente al evento TRIM_MEMORY_COMPLETE.
Determinación de Memoria Disponbile
Android establece un límite estricto en el tamaño del heap para cada aplicación, permitiendo la ejecución simultánea de múltiples procesos. El límite exacto varía según la memoria RAM disponible del dispositivo. Si la aplicación alcanza la capacidad del heap y intenta asignar más memoria, el sistema抛出 OutOfMemoryError.
Para evitar errores de memoria en tiempo de ejecución, consulte al sistema la cantidad de heap disponible mediante getMemoryInfo(). Este método retorna un objeto ActivityManager.MemoryInfo con información sobre el estado actual de memoria del dispositivo, incluyendo memoria disponible, memoria total y el umbral de memoria donde el sistema comienza a terminar procesos.
Ejemplo de implementación:
1 public void ejecutarOperacionIntensiva() {
2 ActivityManager.MemoryInfo infoMemoria = obtenerEstadoMemoria();
3
4 if (!infoMemoria.lowMemory) {
5 // Ejecutar operación que requiere memoria
6 }
7 }
8
9 private ActivityManager.MemoryInfo obtenerEstadoMemoria() {
10 ActivityManager manager =
11 (ActivityManager) getSystemService(ACTIVITY_SERVICE);
12 ActivityManager.MemoryInfo info =
13 new ActivityManager.MemoryInfo();
14 manager.getMemoryInfo(info);
15 return info;
16 }
Estructuras de Código Optimizadas para Memoria
Ciertas características de Android, clases Java y patrones de código consumen más memoria que otras. Puede minimizar el consumo seleccionando alternativas más eficientes.
Uso Prudente de Services
Mantener un servicio ejecutándose cuando no es necesario representa uno de los errores más graves en gestión de memoria. Si la aplicación requiere un servicio para trabajo en segundo plano, manténgalo activo solo cuando sea necesario y deténgalo una vez completada la tarea para evitar fugas de memoria.
El sistema tienden a mantener el proceso del servicio activo, consumiendo memoria que otros procesos podrían utilizar. Esto reduce la caché de procesos en el LRU del sistema, haciendo menos eficiente el cambio entre aplicaciones.
Se recomienda evitar servicios persistentes y considerar alternativas como JobScheduler. Si requiere utilizar un servicio, IntentService representa la mejor opción ya que se detiene automáticamente tras procesar el intent que lo inició.
Contenedores de Datos Optimizados
Algunas clases proporcionadas por lenguajes de programación no están optimizadas para dispositivos móviles. Por ejemplo, la implementación estándar de HashMap puede ser ineficiente en memoria.
Android proporciona contenedores optimizados como SparseArray, SparseBooleanArray y LongSparseArray, que evitan el autoboxing de claves y valores (conversión de tipos primitivos a objetos), reduciendo la creación de objetos adicionales.
Abstracciones con Moderación
Los desarrolladores utilizan abstracciones como buena práctica de programación para mejorar la flexibilidad y mantenibilidad del código. Sin embargo, las abstracciones tienen un costo significativo: requieren más código para ejecutarse, más tiempo y más RAM para mapeo en memoria. Evite abstracciones que no proporcionen beneficios sustanciales.
Protocol Buffers Nano
Protocol Buffers es un mecanismo de serialización de datos estructurados desarrollado por Google. Si utiliza Protocol Buffers, emplee la versión nano en código cliente. Las versiones regulares generan código redundante que puede causar incremento en uso de RAM, crecimiento del APK y menor rendimiento.
Prevención de Fragmentación de Memoria
Los eventos de recolección de basura generalmente no afectan el rendimiento de la aplicación. Sin embargo, múltiples eventos de basura en períodos cortos pueden ocupar el tiempo de frames disponibles. El tiempo dedicado a recolección resta capacidad para procesamiento de renderizado o audio streaming.
La fragmentación de memoria ocurre cuando se crea un número excesivo de objetos temporales en un período короткий. Por ejemplo, dentro de un bucle for o en el método onDraw() de una vista creando nuevos objetos Paint o Bitmap. Estos objetos pueden agotar rápidamente la memoria disponible, forzando eventos de recolección de garbage.
Utilice Memory Profiler de Android Studio para identificar áreas con alta fragmentación. Una vez identificadas, reduzca las asignaciones en regiones críticas moviendo objetos fuera de bucles internos o utilizandopatrones de fábrica.
Eliminación de Recursos y Librerías Intensivas
Algunos recursos y librerías en su código pueden consumir memoria sin conocimiento. El tamaño total del APK, incluyendo librerías de terceros y recursos incrustados, afecta directamente el consumo de memoria.
Reducción del Tamaño del APK
Reducir el tamaño de la aplicación disminuye significativamente el uso de memoria. El tamaño de bitmaps, recursos, frames de animación y librerías de terceros influyen en el tamaño del APK. Android Studio y el SDK proporcionan herramientas para reducir el tamaño de recursos y dependencias.
Inyección de Dependencias con Dagger2
Los frameworks de inyección de dependencias simplifican el código y proporcionan un entorno adaptable. Si necesita inyección de dependencias, use Dagger2, que no utiliza reflexión para escanear código. Su implementación estática en tiempo de compilación se aplica a aplicaciones Android sin开销 adicional de rendimiento o memoria.
Otros frameworks que utilizan reflexión tienden a consumir significativamente más ciclos de CPU y RAM durante la inicialización, causando retrasos notables al iniciar la aplicación.
Uso Cauteloso de Librerías Externas
El código de librerías externas frecuentemente no está diseñado para entornos móviles y puede ser ineficaz. Antes de usar una librería, optimícela para dispositivos móviles considerando el tamaño del código y huella de memoria.
Incluso librerías optimizadas pueden causar conflictos. Por ejemplo, una librería puede implementar nano Protocol Buffers mientras otra usa micro, generando dos implementaciones diferentes de Protocol Buffers en la aplicación. Esto ocurre con diferentes frameworks de logging, análisis, carga de imágenes, caché y otras implementaciones.
Aunque ProGuard puede移除 API y recursos con marcas correctas, no elimina dependencias internas grandes de las librerías. Esto se vuelve problemático cuando las librerías utilizan subclases de Activity con amplias cadenas de dependencia o reflexión extensive.
Evite compartir librerías por una o dos características necesarias. No desirée importar código no utilizado. Busque implementaciones que coincidan estrechamente con sus necesidades o considere crear su propia implementación.