Desarrollando para Android - Optimizando la Memoria.


1. Evite asignaciones o creación de instancias en lazos internos.

En la medida de lo posible evitar instanciar objetos o hacer asignaciones dentro de bucles o iteraciones internas que se ejecuten repetidamente. Es preferible definir el objeto o asignación a nivel de clase.

Ejemplo: onDraw() es un método que se ejecuta constatemente cuando personalizas la vista, en ese caso se debería evitar realizar alguna asignación adentro.

2. Evitar las asignaciones tanto como sea posible.

Algunas estratégias en Android son poco ortodoxas si las comparamos desde el punto de vista de un desarrollo tradicional Java, sin embargo en aras del rendimiento de la aplicación (performance) son aceptables y se perdonan. Estas son las estrategias.

  • Objetos cacheados: Consiste en la reutilización de objetos declarandolos como variables globales. También se pueden declarar como variables estáticas pero se debe tener cuidado con las múltiples instancias de actividades.
  • Pools de objetos: Cuando es necesario coordinar uno o más objetos de cierto tipo temporalmente. Se debe tener cuidado con los escenarios de concurrencia y es recomendable combinarlo con una estrategia de cache. La ventaja es considerable cuando se trabaja con dispositivos limitados de memoría.
  • Arrays: ArrayList es tipo de collección recomendada. Si la colección no necesita tener un tamaño dinámico se recomienda usar un arreglo con el tamaño definido.
  • Clases de colecciones de la API de Android: Es recomendable en lugar de utilizar HashMap de la API Standar emplear ArrayMap o SimpleArrayMap para manejar estructuras de datos ya que son clases más optimas y eficientes con el manejo de memoría.
  • Evitar usar tipos de objetos si es posible usar tipos de primitivas: Esta recomendación es heredada de las buenas prácticas de la API Standar.Usar los Wrappers en lugar de las primitivas es más costoso. Recuerden el autoboxing y el consumo de memoria que implica usar el objeto en lugar de la primitiva.
  • Evitar arreglos de objetos: Si es posible usar arreglos de primitivas en lugar de arreglo de objetos. Es más costos tener un arreglo de Point[] en lugar de int[] points.
  • Métodos con objetos mutados: Usar un objeto como parámetro que pueda ser mutado es mejor que retornar un objeto para luego ser asignado. 
          En lugar de hacer esto:
                    Rect getRect(int w, int h).
          Hacer esto:
                            void getRect(Rect rect, int w, int h).

3. Evitar los Iterators.

La iteración a través de índices ayuda a evitar el número de asignaciones. Como se describe en los items anteriores lo recomendable es evitar las asignaciones sobre todo en los lazos internos y recurrentes.
Según esto es mejor usar:
final int count = myList.size();
    for (int i = 0; i < count; ++i) {
        Object o = myList.get(i);
        // …
    }
Que List.iterator() ó (Object o : myObjects), ya que en estos últimos dos casos la iteración requiere un poco más de memoria ya que siempre hará asignación de espacio aún cuando la lista a iterar sea vacia.

4. Evitar los Enums.

Los Enum son usados para representar constantes, pero son más costosos que usar representaciones de tipo primitivas en términos de memoria y código. No es gran lio si se utilizan, pero podrían serlo cuando son usados en exceso en grandes aplicaciones o cuando forman parte de una API o Core que será usado por muchas otras aplicaciones. Recomiendan si es posible usar datos tipo "int" en su lugar.

5. Evitar frameworks y librerías no escritas para aplicaciones móviles.

Muchas veces decidimos usar una librería de terceros para solucionar un issue en particular, pero no es recomendable agregar al proyecto una librería que pesa bastante y posee cientos de utilidades para finalmente usarla sólo en una línea de código. Si vas adicionar librerías externas asegurate de al menos usar el 70% de ella, de lo contrario es más rentable implementar en la aplicación el issue sin a recurrir librerías o frameworks externos.

6. Evitar Static Leaks.

Los objetos estáticos pueden ser útiles para evitar asignaciones innecesarias, sin embargo hay que ser cuidadosos teniendo en cuenta que el tiempo de vida de un Activity no es similar al tiempo de vida de una variable u objeto estática, esto podría incurrir en leaks (crashes/rupturas) dentro de la aplicación.

7. Evitar Finalizer.

En el escenario de Android no se recomienda usar el finalizer, en lugar de ello considere implementar la interface AutoCloseable y liberar recursos a través del método close().

8. Evitar el exceso de inicializaciones.

Se recomienda cargar en memoria o inicializar los objetos hasta que realmente sea necesario. El exceso de inicialización de variables innecesariamente ocasiona problemas de rendimiento en momentos vitales del ciclo de vida de la aplicación.

9. Reserve la cache por demanda.

A partir de la API 14 se encuentra disponible el callback onTrimMemory() que provee la interface ComponentCallbacks2, que le permitirá liberar memoria cuando el sistema la requiera.  Ejemplo práctico.

10. Usar isLowRamDevice.

Existe una operación que permite detectar cuando su aplicación se ejecuta en un dispositivo con memoria limitada (normalmente la memoria disponible es 512MB o menos). Esta operación se llama isLowRamDevice()  y pertenece a la clase ActivityManager.

Es muy útil para tomar desiciones en la aplicación sobre desactivar funciones que requieran más memoria de lo que razonablemente esté disponible en un dispositivo.

11. Evite solicitar la máxima capacidad de memoria.

Las aplicaciones a través de la configuración del AndroidManifest.xml puede solicitar usar la máxima capacidad de memoria RAM, pero no se debería hacer. Esta propiedad está destinada sólo para un pequeño conjunto de aplicaciones que pueden justificar la necesidad de consumir más memoria RAM como por ejemplo un App de edición de una muchas fotografías.

Las aplicaciones que solicitan aumentar su capacidad de utilización de memoria corren el riesgo de limitar la memoria disponible para ejecutar otros procesos en el dispositivo.

12. Evitar correr innecesariamente servicios por largos periodos de tiempo.

Los servicios son procesos que se ejecutan en un segundo plano y si no se gestionan correctamente pueden quedar corriendo indefinidamente consumiendo recursos de manera innecesaria. Lo recomendable es garantizar que el servicio siempre sea apagado una vez finalice su trabajo.

13. Optimice el tamaño del código.

  • Use Proguard: Esto trae como ventajas reducir el tamaño del apk, mejorar el performance y la ofuscación (Obfuscation), es decir evitar la ingeniería inversa del compilado.
  • Sea cuidadoso con las librerías externas: Recuerde la regla del 70% : Si vas a usar una librería externa, que al menos la mayoría de sus utilidades sean usadas.
  • Sea simple con el código: Las soluciones claras y simples en la mayoría de los casoso son mas rentables que las abstractas y con grande lotes de código.

La fuente del presente artículo corresponde a @chethaase:

Thanks for your comment