c# - ¿Por qué obtengo una OutOfMemoryException cuando tengo imágenes en mi ListBox?
out-of-memory windows-phone-8 (3)
Quiero mostrar todas las imágenes almacenadas en la carpeta de fotos de Windows Phone 8 en mi galería personalizada que usa un ListBox
para mostrar las imágenes.
El código ListBox
es el siguiente:
<phone:PhoneApplicationPage.Resources>
<MyApp:PreviewPictureConverter x:Key="PreviewPictureConverter" />
</phone:PhoneApplicationPage.Resources>
<ListBox Name="previewImageListbox" VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel CleanUpVirtualizedItemEvent="VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1">
</VirtualizingStackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Image Source="{Binding Converter={StaticResource PreviewPictureConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Con el siguiente convertidor:
public class PreviewPictureConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
PreviewImageItem c = value as PreviewImageItem;
if (c == null)
return null;
return c.ImageData;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Las imágenes se almacenan en una clase personalizada:
class PreviewImageItem
{
public Picture _picture = null;
public BitmapImage _bitmap = null;
public PreviewImageItem(Picture pic)
{
_picture = pic;
}
public BitmapImage ImageData
{
get
{
System.Diagnostics.Debug.WriteLine("Get picture " + _picture.ToString());
_bitmap = new BitmapImage();
Stream data = _picture.GetImage();
try
{
_bitmap.SetSource(data); // Out-of memory exception (see text)
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Exception : " + ex.ToString());
}
finally
{
data.Close();
data.Dispose();
data = null;
}
return _bitmap;
}
}
}
El siguiente código se usa para configurar la fuente de datos ListBox
:
private List<PreviewImageItem> _galleryImages = new List<PreviewImageItem>();
using (MediaLibrary library = new MediaLibrary())
{
PictureCollection galleryPics = library.Pictures;
foreach (Picture pic in galleryPics)
{
_galleryImages.Add(new PreviewImageItem(pic));
}
previewImageListbox.ItemsSource = _galleryImages;
};
Finalmente aquí está el código de "limpieza":
private void VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1(object sender, CleanUpVirtualizedItemEventArgs e)
{
PreviewImageItem item = e.Value as PreviewImageItem;
if (item != null)
{
System.Diagnostics.Debug.WriteLine("Cleanup");
item._bitmap = null;
}
}
Todo esto funciona bien, pero el código falla con una OutOfMemoryException
después de algunas imágenes (especialmente cuando se desplaza rápidamente). El método VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1
se denomina regular (por ejemplo, cada 2 o 3 entradas de listbox) cuando se desplaza ListBox
.
¿Qué pasa con este código de muestra?
¿Por qué la memoria no se libera (lo suficientemente rápido)?
¡Oh, recientemente maté todo el día para que esto funcione!
Entonces la solución es:
Haga que su Imagen controle los recursos gratuitos. Así que configura el
BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;
como se mencionó antes.
Asegúrese de virtualizar _bitmap en cada elemento de la lista. Debe cargarlo bajo demanda (método LongListSelector.Realized) y debe destruirlo. No se recogerá automáticamente y GC.Collect tampoco funciona. La referencia nula tampoco funciona :( Pero aquí está el método: Haga un archivo de 1x1 píxel. Cópielo en un ensamblaje y haga un flujo de recursos del mismo para eliminar sus imágenes con un pixel en blanco de 1x1. Enlace el método personalizado para el evento LongListSelector.UnRealized (p. El contenedor maneja su elemento de la lista).
public static void DisposeImage(BitmapImage image)
{
Uri uri= new Uri("oneXone.png", UriKind.Relative);
StreamResourceInfo sr=Application.GetResourceStream(uri);
try
{
using (Stream stream=sr.Stream)
{
image.DecodePixelWidth=1; //This is essential!
image.SetSource(stream);
}
}
catch { }
}
Trabajando para mí en LongListSelector con 1000 imágenes de ancho 400 cada una.
Si pierde el paso 2 con la recopilación de datos, puede ver los buenos resultados, pero la memoria se desborda después de que se hayan desplazado entre 100 y 200 elementos.
Acaba de tener Windows Phone con mostrar todas las imágenes en la carpeta "imágenes" de la biblioteca de medios del usuario en la pantalla. Eso es increíblemente intensivo en memoria y teniendo en cuenta el límite de 150 MB en aplicaciones WP8, no es de extrañar que obtenga excepciones OOM.
Algunas cosas que debes considerar agregar:
1) Establezca las propiedades Source y SourceUri en null cuando se desplace el elemento del cuadro de lista fuera de la vista. Consulte "Caché de imágenes" en el artículo de Stefan aquí http://blogs.msdn.com/b/swick/archive/2011/04/07/image-tips-for-windows-phone-7.aspx
BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;
2) Si estás en WP8, asegúrate de configurar DecodePixelWidth y / o DecodePixelHeight. De esta forma, se cargará una imagen en la memoria, se redimensionará permanentemente y solo se almacenará en la memoria la copia redimensionada. Las imágenes cargadas en la memoria pueden ser mucho más grandes que el tamaño de la pantalla del teléfono. Por lo tanto, recortarlos hasta el tamaño correcto y solo almacenar las imágenes redimensionadas es vital. Establezca BitmapImage.DecodePixelWidth = 480 (como máximo) para ayudar con eso.
var bmp = new BitmapImage();
// no matter the actual size,
// this bitmap is decoded to 480 pixels width (aspect ratio preserved)
// and only takes up the memory needed for this size
bmp.DecodePixelWidth = 480;
bmp.UriSource = new Uri(@"Assets/Demo.png", UriKind.Relative);
ImageControl.Source = bmp;
(muestra de código de here )
3) ¿Por qué está utilizando Picture.GetImage () en lugar de Picture.GetThumbnail ()? ¿Realmente necesitas la imagen para ocupar toda la pantalla?
4) Considere pasar de ListBox a LongListSelector si se trata de una aplicación exclusiva de WP8. LLS tiene mucha, mucha mejor virtualización que ListBox. En cuanto a la muestra del código, puede ser suficiente que simplemente cambie su elemento XAML ListBox al elemento LongListSelector.
Pruebe este enfoque: Descarga de imágenes con limpieza automática de la memoria . Proyecto de muestra aquí: https://simca.codeplex.com/