2012-12-11 115 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

相关问题