2012-12-11 152 views
26

我想顯示存儲在我的自定義圖庫中的Windows Phone 8照片文件夾中的所有圖像,該圖像使用ListBox來顯示圖像。當我的ListBox中有圖像時,爲什麼會出現OutOfMemoryException?

ListBox代碼如下:

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

用下面的轉換器:

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

圖像存儲在自定義類:

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

下面的代碼被用於設置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; 
}; 

最後這裏是「清理」代碼:

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

這一切工作正常,但代碼之後的幾個圖像與OutOfMemoryException崩潰(滾動快尤其是當)。當滾動ListBox時,方法VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1被稱爲regulary(例如,每2或3個列表框條目)。

這個示例代碼有什麼問題?

爲什麼內存不能釋放(速度不夠快)?

+0

什麼是'圖片'和'GetImage()'方法做什麼?你只能將'_bitmap'字段設置爲'null',但是'_picture'字段是單獨存在的,它可能是保存一些數據的對象嗎?另外,公開公開領域並不是一個好習慣。在'PreviewImageItem'中實現'IDisposable'並在'VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1'方法中調用'Dispose()'... – khellang

+0

在清理過程中,您應該取消'_picture'屬性。 –

+0

圖片的類型爲「Microsoft.Xna。 Framework.Media.Picture「並且不需要太多內存。大部分內存由BitmapImages使用,它們是由Picture對象提供的流創建的。 – Hyndrix

回答

22

哦,我最近殺了整整一天,使這個工作!

所以解決方案是:

使您的圖像控制免費資源。所以設置

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

如前所述。

確保虛擬化列表中每個項目的_bitmap。你應該根據需要加載它(LongListSelector.Realized方法),你必須銷燬它!它不會自動收集,GC.Collect也不會工作。 空引用不工作:( 但是這裏是方法: 製作1x1像素文件。將其複製到程序集中,並從中創建資源流,以1x1像素空白處理您的圖像。將自定義處理方法綁定到LongListSelector.UnRealized事件(e.Container處理您的列表項)。

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

我在LongListSelector 1000張圖片400寬度的每個工作。

如果你錯過了2步與數據收集可以看到的好成績但滾動100-200項後內存溢出

+0

我再次遇到內存問題。解決它的唯一方法是使用「DisposeImage」方法! – Hyndrix

+1

很高興它可以幫助你。我認爲這是WP8平臺中的一個bug。 –

+0

我面臨同樣的問題。感謝您的解決方案。 –

13

你剛剛用Windows Phone在屏幕上顯示用戶的媒體庫「圖片」文件夾中的所有圖片。這是令人難以置信的內存密集型,考慮到WP8應用程序的150MB限制,這也難怪你會遇到OOM異常。

你應該考慮增加有幾件事情:

1)設置源和SourceUri屬性滾動ListBoxItem的拿出來看的時候空。見Stefan的文章「緩存圖像」在這裏@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)如果你在WP8請務必設定DecodePixelWidth和/或DecodePixelHeight。這樣圖像將被加載到內存中,永久調整大小,只有調整大小的副本被存儲在內存中。加載到內存中的圖像可能比手機本身的屏幕尺寸大得多。所以裁剪到合適的尺寸,只存儲調整大小的圖像至關重要。設置BitmapImage.DecodePixelWidth = 480(最大值)來幫助解決這個問題。

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; 

(從here代碼示例)

3)你爲什麼要使用Picture.GetImage()而不是Picture.GetThumbnail()?你真的需要這個形象來佔據整個屏幕嗎?

4)如果這是一個WP8獨佔應用程序,請考慮從ListBox移動到LongListSelector。 LLS比ListBox有更好的虛擬化。看看你的代碼示例,只需要將XAML ListBox元素更改爲LongListSelector元素即可。

+0

限制解碼分辨率的提示是偉大的(我完全忘記了這一點,雖然它是如此明顯)。縮略圖流質量太低。有一件事情也很重要,就是在快速滾動的情況下調用System.GC.Collect()之後。 – Hyndrix

+0

我有些困惑,我的ListView通過數據綁定獲取數據,因此我對滾動沒有任何直接影響。使用技巧1.我可以卸載圖像,但如果用戶向後滾動,則圖像現在變黑並且不會再被框架加載......任何想法? –

+0

Tim,如果你在WP8上,你應該使用帶有ItemRealized和ItemUnrealized事件的LongListSelector。 – JustinAngel

相關問題