wake-up-neo.net

Warum erhalte ich eine OutOfMemoryException, wenn ich Bilder in meiner ListBox habe?

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

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

Zum Schluss hier der Bereinigungscode:

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 dies funktioniert gut, aber der Code stürzt nach ein paar Bildern mit einer OutOfMemoryException ab (besonders beim schnellen Scrollen). Die Methode VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1 wird regelmäßig aufgerufen (z. B. alle 2 oder 3 Listenfeldeinträge), wenn die ListBox gescrollt wird.

Was ist los mit diesem Beispielcode?

Warum wird der Speicher nicht schnell genug freigegeben?

27
Hyndrix

Oh, ich habe vor kurzem den ganzen Tag umgebracht, damit das funktioniert!

Die Lösung lautet also:

Machen Sie Ihre Bildsteuerungsressourcen frei. Also das einstellen 

BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;

wie es schon erwähnt wurde. 

Stellen Sie sicher, dass Sie _bitmap für jedes Element der Liste virtualisieren. Sie sollten es bei Bedarf laden (Methode LongListSelector.Realized) und Sie müssen es zerstören! Es wird nicht automatisch gesammelt und GC.Collect funktioniert auch nicht. Nullreferenz funktioniert auch nicht :( Aber hier ist die Methode: Machen Sie 1x1 Pixel Kopieren Sie sie in Assembly und machen Sie einen Ressourcenstrom daraus, um Ihre Bilder mit einem leeren Pixel von 1 x 1 zu löschen. Binden Sie die benutzerdefinierte Entsorgungsmethode an das LongListSelector.UnRealized-Ereignis (e.Container behandelt Ihr Listenelement). 

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

Für mich in LongListSelector arbeiten mit 1000 Bildern jeweils 400 Breite.

Wenn Sie den zweiten Schritt bei der Datenerfassung verpassen, können Sie die guten Ergebnisse sehen, aber der Speicher läuft nach 100-200 Elementen über. 

23
gleb.kudr

Sie hatten gerade Windows Phone, um alle Bilder im Ordner "Bilder" der Medienbibliothek eines Benutzers auf dem Bildschirm anzuzeigen. Das ist unglaublich speicherintensiv und angesichts der Beschränkung auf 150 MB für WP8-Apps ist es kein Wunder, dass Sie OOM-Ausnahmen erhalten. 

Ein paar Dinge, die Sie hinzufügen sollten:

1) Setzen Sie die Eigenschaften Source und SourceUri auf null, wenn Sie das Listbox-Element außerhalb der Ansicht verschieben. Siehe "Caching Images" 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 sich für WP8 befinden, stellen Sie sicher, dass DecodePixelWidth und/oder DecodePixelHeight eingestellt ist. Auf diese Weise wird ein Bild in den Speicher geladen, die Größe wird dauerhaft geändert, und nur die Kopie wird im Speicher gespeichert. Die in den Speicher geladenen Bilder können viel größer sein als die Bildschirmgröße des Telefons. Daher ist es äußerst wichtig, diese auf die richtige Größe zuzuschneiden und nur die Größe der 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 dies eine exklusive WP8-App ist. LLS bietet eine viel bessere Virtualisierung als ListBox. Wenn Sie Ihr Codebeispiel betrachten, reicht es möglicherweise aus, dass Sie Ihr XAML ListBox-Element in LongListSelector-Element ändern. 

13
JustinAngel

Versuchen Sie diesen Ansatz: Image Downloader mit automatischer Speicherreinigung . Beispielprojekt hier: https://simca.codeplex.com/

0