[C#] Warum erhalte ich eine OutOfMemoryException, wenn ich Bilder in meiner ListBox habe?


Answers

Sie hatten nur Windows Phone mit allen Bildern im Ordner "Bilder" einer Benutzer-Mediathek auf dem Bildschirm. Das ist unglaublich speicherintensiv und angesichts der 150MB-Grenze für WP8-Apps ist es kein Wunder, dass Sie OOM-Ausnahmen bekommen.

Ein paar Dinge, die Sie berücksichtigen sollten:

1) Setzen Sie die Source- und SourceUri-Eigenschaften auf Null, wenn Sie die Listbox-Elemente außerhalb der Ansicht scrollen. Siehe "Zwischenspeichern von Bildern" in Stefans Artikel hier @ 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) Wenn Sie auf WP8 sind, stellen Sie sicher, dass DecodePixelWidth und / oder DecodePixelHeight eingestellt sind. Auf diese Weise wird ein Bild in den Speicher geladen, permanent in der Größe verändert und nur die skalierte Kopie wird gespeichert. Die in den Speicher geladenen Bilder können viel größer als die Bildschirmgröße des Telefons selbst sein. Daher ist es wichtig, diese auf die richtige Größe zuzuschneiden und nur die neu skalierten Bilder zu speichern. Stellen Sie BitmapImage.DecodePixelWidth = 480 (maximal) ein, um dies zu unterstützen.

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;

(Codebeispiel von hier )

3) Warum verwenden Sie Picture.GetImage () anstelle von Picture.GetThumbnail ()? Benötigen Sie wirklich das Bild, um den gesamten Bildschirm aufzunehmen?

4) Ziehen Sie in Betracht, von ListBox zu LongListSelector zu wechseln, wenn es sich um eine exklusive WP8-App handelt. LLS hat viel, viel bessere Virtualisierung als ListBox. Wenn Sie sich Ihr Codebeispiel ansehen, reicht es möglicherweise aus, wenn Sie Ihr XAML-ListBox-Element einfach in ein LongListSelector-Element ändern.

Question

Ich möchte alle Bilder anzeigen, die im Windows Phone 8- ListBox in meiner benutzerdefinierten Galerie gespeichert sind, die eine ListBox zum Anzeigen der Bilder verwendet.

Der ListBox Code lautet wie folgt:

    <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>

Mit folgendem Konverter:

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();
    }
}

Bilder werden in einer benutzerdefinierten Klasse gespeichert:

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;
        }
    }
}

Der folgende Code wird zum ListBox Datenquelle " ListBox verwendet:

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;
};

Schließlich ist hier der "Aufräum" -Code:

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;
    }
}

All das funktioniert gut, aber der Code stürzt nach einigen Bildern mit einer OutOfMemoryException ab (besonders beim schnellen Scrollen). Die Methode VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1 wird regelmäßig aufgerufen (z. B. alle 2 oder 3 Listbox-Einträge), wenn die ListBox wird.

Was ist mit diesem Beispielcode falsch?

Warum wird der Speicher nicht (schnell genug) freigegeben?