[Windows Phone 8 ] Tip: Memoria y trabajo con imágenes.

Hola a todos!

En las plataformas móviles como Windows Phone 8 o Windows Store, nuestras aplicaciones tienen unas restricciones claras en el uso de memoria, a diferencia de los sistemas de escritorio que podían ocupar toda la memoria disponible en el sistema.

Hay muchas formas de controlar la memoria que usamos en nuestra aplicación. Aplicar bloques using, usar Dispose correctamente… Pero cuando trabajamos con imágenes, con objetos WriteableBitmap, BitmapImage o BitmapSource, descubriremos que nada de esto sirve.

Por norma general, si desarrollamos bien nuestro código, iremos aumentando el uso de la memoria hasta que llegue un punto en el que, debido a la presión que estamos ejerciendo sobre ella, el recolector de basura (GC, Garbage Collector) entre en escena y libere la memoria no usada.

El problema es que los objetos WriteableBitmap, BitmapImage o BitmapSource en Windows Phone 8 y Windows Store, realmente ocupan muy poca memoria manejada, apenas unos cuantos Kb. Realmente el peso de la memoria es nativo, no manejado en el objeto, ya que la imagen está almacenada en memoria nativa. Por esta razón, el Garbage Collector no es presionado a lanzarse, su memoria manejada no crece peligrosamente. Sin embargo el sistema operativo tiene en cuenta tanto la memoria nativa como la manejada para calcular nuestra cuota de uso.

Una práctica que muchos desarrolladores suelen llevar a cabo llegado este punto, es forzar la recolección de memoria invocando manualmente al Garbage Collector. Esto está totalmente desaconsejado, puesto que en primer lugar va a ralentizar nuestra app muchísimo durante la recolección, pero además puede tener efectos secundarios en la ejecución de nuestra app. Lo mejor, es que existe una forma de evitar esto: hacer que el GC se entere de la memoria que realmente estamos usando.

Existen dos métodos en la clase GC, que son dos grandes desconocidos: AddMemoryPressure y RemoveMemoryPressure. Estos dos métodos nos permiten informar al runtime de grandes bloques de memoria nativa que está siendo usada o que está dejando de ser usada, respectivamente, de forma que el runtime pueda tener en cuenta esta memoria para programar los ciclos de ejecución del recolector de basura. Ambos reciben un solo parámetro, de tipo long, que indica los bytes que estamos añadiendo a la memoria.

De esta forma, cuando por ejemplo carguemos una imagen en memoria y la añadamos a una lista, debemos indicar al sistema el peso que estamos añadiendo en la memoria usando AddMemoryPressure:

AddMemoryPressure
void task_Completed(object sender, PhotoResult e)
{
    WriteableBitmap bmp = new WriteableBitmap(100, 100);
    bmp.SetSource(e.ChosenPhoto);
    listImages.Items.Add(bmp);
    GC.AddMemoryPressure(40000000);
}

De la misma forma, cuando eliminemos o dejemos de usar esa imagen, podemos informar al runtime de que la hemos liberado:

RemoveMemoryPressure
private void RemoveImage()
{
    listImages.Items.RemoveAt(0);
    GC.RemoveMemoryPressure(40000000);
}

Y Voila! Permitimos que sea el sistema el que se ocupe de organizar las recolecciones de memoria y mantener la misma siempre saneada. Sin tener que forzarlas por nuestra parte ni sufrir la lentitud y efectos secundarios de ello.

Un pequeño tip, que espero que os ayude en vuestros desarrollos si trabajáis con muchas imágenes dinámicas o editando imágenes grandes.

Más información: AddMemoryPressure y RemoveMemoryPressure

Un saludo y Happy Coding!

 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *