2012-12-11 8 views
26

Ich möchte alle Bilder anzeigen, die im Windows Phone 8-Fotoordner in meiner benutzerdefinierten Galerie gespeichert sind, die eine ListBox für die Anzeige der Bilder verwendet.Warum erhalte ich eine OutOfMemoryException, wenn ich Bilder in meiner ListBox habe?

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 in einer benutzerdefinierten Klasse gespeichert sind:

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 verwendet wird, um die ListBox Datenquelle festzulegen:

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 die „Bereinigung“ 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; 
    } 
} 

Das alles funktioniert gut, aber der Code stürzt mit einem OutOfMemoryException nach ein paar Bilder (vor allem, wenn schnell Scrollen). Die Methode VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1 wird regelmäßig aufgerufen (z. B. alle 2 oder 3 Listbox-Einträge), wenn die ListBox gescrollt wird.

Was ist falsch an diesem Beispielcode?

Warum wird Speicher nicht freigegeben (schnell genug)?

+0

Was ist 'Bild' und was macht die' GetImage() 'Methode? Sie setzen das '_bitmap'-Feld nur auf 'null', aber das' _picture'-Feld bleibt allein, könnte es das Objekt sein, das einige Daten enthält? Es ist auch keine gute Vorgehensweise, Felder öffentlich zugänglich zu machen. Implementieren Sie "IDisposable" in "PreviewImageItem" und nennen Sie 'Dispose()' in Ihrer 'VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1' Methode ... – khellang

+0

In der Bereinigung sollten Sie auch die' _picture' Eigenschaft aufheben –

+0

Das Bild ist vom Typ "Microsoft.Xna. Framework.Media.Picture "und benötigt nicht viel Speicher. Der meiste Speicher wird von den BitmapImages verwendet, die aus den Streams erstellt werden, die von den Picture-Objekten bereitgestellt werden. – Hyndrix

Antwort

22

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

So ist die Lösung:

Machen Sie Ihre Image-Steuerelement freie Ressourcen. Stellen Sie also die

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

wie zuvor erwähnt.

Stellen Sie sicher, dass Sie _bitmap für jedes Element der Liste virtualisieren. Sie sollten es bei Bedarf laden (LongListSelector.Realized-Methode) und Sie müssen es zerstören! Es wird nicht automatisch gesammelt und GC.Collect funktioniert auch nicht. Nullreferenz funktioniert nicht auch :( Aber hier ist die Methode: Machen Sie 1x1 Pixel-Datei. Kopieren Sie es in Assembly und Ressourcen-Stream daraus zu entsorgen Sie Ihre Bilder mit 1x1 Pixel leer. Binden benutzerdefinierte dispose-Methode zu LongListSelector.UnRealized Ereignis (e.Container Griffe Liste Artikel).

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

Arbeiten für mich in LongListSelector mit 1000 Bildern 400 Breite je.

Wenn Sie den 2 Schritt mit der Datensammlung verpassen können Sie die die guten Ergebnisse sehen aber der Speicher überläuft nach 100-200 gescrollten Elementen

+0

Ich kam wieder über das Speicherproblem. Und das einzige Mittel, das es gelöst hat, war die Verwendung Ihrer "DisposeImage" -Methode! – Hyndrix

+1

Froh, dass es dir hilft. Ich denke, das ist ein Fehler in der WP8-Plattform. –

+0

Ich stehe vor dem gleichen Problem. Danke für die Lösung. –

13

Sie hatten gerade Windows Phone mit zeigen alle Bilder in einem Medienordner "Bilder" des Benutzers auf dem Bildschirm. Das ist unglaublich speicherintensiv und angesichts der 150 MB-Grenze für WP8-Apps ist es kein Wunder, dass Sie OOM-Ausnahmen bekommen.

Ein paar Dinge, die Sie berücksichtigen sollten, und fügte hinzu:

1) Quelle festlegen und SourceUri Eigenschaften auf null, wenn die ListBoxItem aus Sicht scrollen. Siehe "Caching 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 sicher DecodePixelWidth und/oder DecodePixelHeight einzustellen. Auf diese Weise wird ein Bild in den Speicher geladen, die Größe wird permanent geändert und nur die geänderte Kopie wird gespeichert. Die Bilder, die in den Speicher geladen werden, können viel größer als die Bildschirmgröße des Telefons selbst sein. Es ist daher wichtig, diese auf die richtige Größe zu schneiden und nur die Größe der Bilder zu speichern. Setzen Sie BitmapImage.DecodePixelWidth = 480 (maximal), um damit zu helfen.

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

3) Warum verwenden Sie Picture.GetImage() anstelle von Picture.GetThumbnail()? Brauchen Sie wirklich, dass das Bild den ganzen Bildschirm einnimmt?

4) Ziehen Sie in Erwägung, 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 Ihr Code-Beispiel betrachten, reicht es möglicherweise aus, dass Sie nur Ihr XAML-ListBox-Element in LongListSelector-Element ändern.

+0

Der Hinweis, die Entschlüsselungsauflösung zu begrenzen, ist groß (ich völlig vergessen, obwohl es so offensichtlich ist). Der Vorschaubild-Stream ist viel zu niedrig. Eine Sache, die auch wichtig ist, ist, System.GC.Collect() nach dem Nullen für den Fall des schnellen Scrollens aufzurufen. – Hyndrix

+0

Ich bin etwas verwirrt, mein ListView bekommt seine Daten per Datenbindung, somit habe ich keinen direkten Einfluss auf das Scrollen. Verwenden der Technik 1. Ich kann die Bilder entladen, aber wenn der Benutzer zurück scrollt, sind die Bilder jetzt schwarz und werden nicht wieder vom Framework geladen ... keine Idee? –

+0

Tim, wenn Sie auf WP8 sind, sollten Sie LongListSelector mit ItemRealized und ItemUnrealized Ereignisse verwenden. – JustinAngel

Verwandte Themen