2012-12-11 9 views
26

Desidero visualizzare tutte le immagini memorizzate nella cartella di foto di Windows Phone 8 nella mia galleria personalizzata che utilizza uno ListBox per la visualizzazione delle immagini.Perché ottengo un OutOfMemoryException quando ho immagini nel mio ListBox?

Il codice ListBox è il seguente:

<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 il seguente convertitore:

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

Le immagini vengono memorizzate in una classe personalizzata:

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

Il codice seguente viene utilizzato per impostare l'origine dati 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; 
}; 

Infine ecco il codice "pulizia":

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

Tutto questo funziona bene, ma il codice si blocca con una OutOfMemoryException dopo un paio di immagini (in particolare durante lo scorrimento veloce). Il metodo VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1 viene chiamato regolarmente (ad esempio ogni 2 o 3 voci della casella di riepilogo) quando si scorre lo ListBox.

Cosa c'è di sbagliato con questo codice di esempio?

Perché la memoria non viene liberata (abbastanza veloce)?

+0

Che cos'è "Immagine" e cosa fa il metodo 'GetImage()'? Si imposta il campo '_bitmap' su' null', ma il campo '_picture' è lasciato in pace, potrebbe essere quell'oggetto che contiene alcuni dati? Inoltre, non è una buona pratica esporre i campi pubblicamente. Implementa 'IDisposable' in' PreviewImageItem' e chiama 'Dispose()' nel tuo metodo 'VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1' ... – khellang

+0

Nella pulizia, dovresti annullare anche la proprietà' _picture' –

+0

L'immagine è di tipo "Microsoft.Xna. Framework.Media.Picture "e non richiede molta memoria. La maggior parte della memoria viene utilizzata da BitmapImages creati dagli stream forniti dagli oggetti Picture. – Hyndrix

risposta

22

Oh, recentemente ho ucciso tutta la giornata per farlo funzionare!

Quindi la soluzione è:

Fai risorse gratuite vostro controllo dell'immagine. Quindi imposta il

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

come è stato menzionato prima.

Assicuratevi di virtualizzare _bitmap su ogni elemento della lista. Dovresti caricarlo su richiesta (metodo LongListSelector.Realized) e devi distruggerlo! Non verrà raccolto automaticamente e GC.Collect non funzionerà. Il riferimento null non funziona :( Ma ecco il metodo: Crea un file pixel 1x1, copialo in assembly e fai flusso di risorse da esso per disporre le tue immagini con uno spazio di pixel 1x1. Bind usa il metodo di eliminazione personalizzato su LongListSelector.UnRealized event (e.Container gestisce l'elemento della 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 { } 
} 

Lavorare per me in LongListSelector con 1000 immagini 400 di larghezza ciascuna.

Se si perde il passo 2 con la raccolta dei dati è possibile vedere i buoni risultati ma la memoria trabocca dopo aver scansionato 100-200 elementi

+0

Mi sono imbattuto di nuovo nel problema della memoria. E l'unico modo per risolverlo era usare il tuo metodo "DisposeImage"! – Hyndrix

+1

Felice che ti aiuti. Penso che sia una specie di bug nella piattaforma WP8. –

+0

Sto affrontando lo stesso problema. Grazie per la soluzione. –

13

Hai appena avuto Windows Phone con mostra tutte le immagini nella cartella "immagini" di una libreria multimediale dell'utente sullo schermo. È incredibilmente intensiva in termini di memoria e considerando il limite di 150 MB per le app WP8 ​​non c'è da meravigliarsi se si ottengono le eccezioni OOM.

Un paio di cose che si dovrebbero prendere in considerazione l'aggiunta di:

1) Imposta origine e le proprietà SourceUri è null quando si scorre la ListBoxItem fuori dal campo visivo. Vedere "Caching immagini" nell'articolo di Stefan qui @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) Se siete su WP8 assicurarsi di impostare DecodePixelWidth e/o DecodePixelHeight. In questo modo un'immagine verrà caricata in memoria, ridimensionata in modo permanente e solo la copia ridimensionata verrà memorizzata. Le immagini caricate in memoria possono essere molto più grandi delle dimensioni dello schermo del telefono stesso. Quindi ritagliare quelli di dimensioni giuste e memorizzare solo le immagini ridimensionate è di vitale importanza. Imposta BitmapImage.DecodePixelWidth = 480 (al massimo) per aiutarti.

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; 

(codice di esempio da here)

3) Perché stai usando Picture.GetImage() al posto di Picture.GetThumbnail()? Hai davvero bisogno dell'immagine per occupare tutto lo schermo?

4) Considerare lo spostamento da ListBox a LongListSelector se si tratta di un'app esclusiva WP8. LLS ha una virtualizzazione molto migliore di ListBox. Guardando il tuo esempio di codice potrebbe essere sufficiente cambiare l'elemento ListBox XAML in elemento LongListSelector.

+0

Il suggerimento per limitare la risoluzione della decodifica è ottimo (l'ho completamente dimenticato, anche se è così ovvio). Il flusso delle miniature è di qualità troppo bassa. Una cosa che è anche importante è chiamare System.GC.Collect() dopo aver annullato il caso di scorrimento veloce. – Hyndrix

+0

Sono un po 'perplesso, il mio ListView ottiene i suoi dati tramite l'associazione dati, quindi non ho alcuna influenza diretta sullo scorrimento. Usando la tecnica 1. Posso scaricare le immagini, ma se l'utente scorre indietro, le immagini sono ora nere e non caricate di nuovo dal framework ... qualche idea? –

+0

Tim, Se sei su WP8 dovresti usare LongListSelector con gli eventi ItemRealized e ItemUnrealized. – JustinAngel

Problemi correlati