27

我想在我的自定义库中显示存储在 Windows Phone 8 照片文件夹中的所有图像,该库使用 aListBox来显示图像。

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在几张图像后崩溃(尤其是在快速滚动时)。该方法在滚动VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1时被定期调用(例如,每 2 或 3 个列表框条目) 。ListBox

这个示例代码有什么问题?

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

4

3 回答 3

23

哦,我最近花了一整天的时间来完成这项工作!

所以解决方案是:

使您的图像控制免费资源。所以设置

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

如前所述。

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

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 个项目后内存溢出。

于 2013-01-08T23:30:26.087 回答
13

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

您应该考虑添加一些内容:

1) 将列表框项目滚动到视图之外时,将 Source 和 SourceUri 属性设置为 null。请在此处查看 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;

(来自这里的代码示例)

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

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

于 2012-12-20T01:00:36.327 回答
0

试试这个方法:带有自动内存清理的图像下载器。此处的示例项目:https ://simca.codeplex.com/

于 2013-10-02T19:41:16.897 回答