C# 当我的列表框中有图像时,为什么会出现OutOfMemoryException?

C# 当我的列表框中有图像时,为什么会出现OutOfMemoryException?,c#,listbox,out-of-memory,windows-phone-8,photo-gallery,C#,Listbox,Out Of Memory,Windows Phone 8,Photo Gallery,我想在自定义图库中显示Windows Phone 8 photo文件夹中存储的所有图像,该图库使用列表框显示图像 列表框代码如下: <phone:PhoneApplicationPage.Resources> <MyApp:PreviewPictureConverter x:Key="PreviewPictureConverter" /> </phone:PhoneApplicationPage.Resources>

我想在自定义图库中显示Windows Phone 8 photo文件夹中存储的所有图像,该图库使用
列表框
显示图像

列表框
代码如下:

    <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>
图像存储在自定义类中:

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;
        }
    }
}
以下代码用于设置
列表框
数据源:

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;
};
所有这些都可以正常工作,但代码在几张图像之后(尤其是快速滚动时)会崩溃,并出现
OutOfMemoryException
。滚动
列表框
时,会定期调用方法
VirtualizangStackPanel\u CleanUpVirtualizedItemEvent\u 1
(例如,每2或3个列表框条目一次)

这个示例代码有什么问题


为什么内存没有被释放(足够快)?

您刚刚使用Windows Phone在屏幕上显示用户媒体库“图片”文件夹中的所有图片。这是难以置信的内存密集型,考虑到WP8应用程序的150MB限制,难怪会出现OOM异常

<> P> >应考虑的几个事项:

1) 将listboxitem滚动出视图时,将Source和SourceUri属性设置为null。请参见Stefan文章中的“缓存图像”@

2) 如果您在WP8上,请确保设置和/或解码像素高度。这样,图像将被加载到内存中,并永久调整大小,并且只有调整大小的副本存储在内存中。加载到内存中的图像可能比手机本身的屏幕大得多。因此,将这些图像裁剪到合适的大小并仅存储大小调整后的图像至关重要。设置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;
(代码示例来自)

3) 为什么要使用Picture.GetImage()而不是()?你真的需要图像占据整个屏幕吗


4)考虑从ListBox移动到LongListSelector,如果这是一个WP8独占应用程序。LLS的虚拟化比ListBox好得多。查看您的代码示例,只需将XAML ListBox元素更改为LongListSelector元素就足够了

哦,我最近花了整整一天的时间才让这一切顺利进行

因此,解决方案是:

使您的图像控件成为免费资源。因此,设定

BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;
如前所述

确保在列表的每个项目上虚拟化_位图。您应该按需加载它(LongListSelector.Realized方法),您必须销毁它!它不会自动收集,GC.collect也不会工作。 空引用也不起作用:( 但方法如下: 创建1x1像素文件。将其复制到程序集中,并从中生成资源流,以处置1x1像素为空的图像。将自定义处置方法绑定到LongListSelector.UnRealized事件(例如,容器处理列表项)

在LongListSelector中为我工作,有1000张图像,每个图像的宽度为400


如果您错过了数据收集的2个步骤,您可以看到良好的结果,但在滚动100-200个项目后内存溢出。

尝试以下方法:此处的示例项目:

什么是
图片
以及
GetImage()的作用是什么
方法如何?您只将
\u bitmap
字段设置为
null
,但
\u picture
字段被单独保留,是否是该对象保存了一些数据?此外,公开字段也不是一个好的做法。在
PreviewImageItem
中实现
IDisposable
,并调用
Dispose()
在您的
VirtualzingStackPanel\u CleanUpVirtualizedItemEvent\u 1
方法中…在清理中,您应该将
\u picture
属性以及图片的类型设置为“Microsoft.Xna.Framework.Media.picture”并且不需要太多内存。大多数内存由BitMapImage使用,BitMapImage是从图片对象提供的流创建的。您的代码中可能有一个明显的错误,我看不到,但您也应该检查这个问题:嗨,Hydrix,您编写的代码对我非常有用。我只想知道您在哪里使用了这个dispose方法,因为您使用的是listbox(因为没有未实现的事件)。您传递的参数是什么。是listbox容器吗?请告诉我,使用虚拟化堆栈面板滚动性能很慢,我如何使其快速。请帮助我。限制解码分辨率的提示非常好(我完全忘记了这一点,尽管这很明显)。缩略图流的质量太低了。还有一件事也很重要,那就是调用System.GC.Collect()在快速滚动的情况下为空后。我有点困惑,我的ListView通过数据绑定获取其数据,因此我对滚动没有任何直接影响。使用技术1.我可以卸载图像,但如果用户向后滚动,图像现在是黑色的,不会被框架再次加载…知道吗?Tim,如果你在WP8上,你知道吗应该将LongListSelector用于ItemAcrealized和ItemUnrealized事件。ListBox的糟糕程度令人难以置信。只需“升级”到LLS,我就解决了我无法解决的各种内存泄漏问题。谢谢!我再次遇到内存问题。唯一解决问题的方法是使用“DisposeImage”方法!很高兴它能帮助你。我认为这是WP8平台中的一个bug。我面临着同样的问题。谢谢你的解决方案。@gleb.kudr你能在这里帮助我吗@Goofy你尝试过我的方法吗?在使用它之后释放每个容器资源吗?
  BitmapImage bitmapImage = image.Source as BitmapImage;
  bitmapImage.UriSource = null;
  image.Source = null;
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;
BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;
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 { }
}