Windows Phone 图片内存使用率高

Windows Phone high memory usage with images

我正在使用 MvvmCross 在 Xamarin 中开发 Windows Phone 应用程序。在此应用程序中,用户从他的 phone 中选择一些图像。它们会显示在列表中,然后用户可以使用它们进行操作。

我正在使用 FileOpenPicker 进行文件选择,并从这些文件中创建 BitmapImages 来显示

foreach (StorageFile file in args.Files) {
                    BitmapImage thumbnail = new BitmapImage();
                    thumbnail.DecodePixelType = DecodePixelType.Physical;
                    try {
                        using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read)) {
                            thumbnail.DecodePixelHeight = 70;
                            thumbnail.DecodePixelWidth = 70;

                            thumbnail.SetSource(fileStream);
                            fileStream.Dispose();
                        }
                    }
                    catch (OutOfMemoryException e) {
                        Mvx.Trace("MEMORY IS FULL");
                    }

在一些其他代码之后,我将这些 BitmapImages 放入 ObservableCollection 中并像这样显示它们

<Image Style="{StaticResource imageListImage}" Source="{Binding Thumbnail}"/>

没什么特别的。 我使用的测试图像总大小为 34 MB。 使用 VS i 的性能和诊断工具能够确定应用程序启动时的内存使用量约为 16 Mb。当我将测试图像加载到应用程序中时,它会飙升至 58 MB。就好像它仍然使用图像的完整尺寸一样。并且(仅用于测试)当我拿走 decodepixelheight 和 width 时,它飙升至大约 350 MB。我完全不知道为什么它为图像使用这么多内存。

因为应用程序必须能够使用更多更大的图像,所以我需要找到一种方法来减少内存使用量。有谁知道我该怎么做?

您的图片在压缩后使用 34 MB 的存储空间。要显示,它们需要解压缩。位图图片每个像素使用 4 个字节(每个颜色通道 RGB 一个字节,加上 alpha 通道一个字节)。 因此,一张 5 兆像素的图片将使用大约 20 MB 的 RAM。 这就是为什么尽可能多地使用 DecodePixelHeight/DecodePixelHeight 是最重要的。

不过,有时您必须 处理大图片(例如 Lumia 1020 的 38 MP 图片,每张 150 MB RAM!!)。为此,诺基亚发布了 Imaging SDK(现在由 Microsoft 维护),允许您处理部分图片,而不必将全部内容加载到内存中。

在您的情况下,主要问题是您一次加载了所有缩略图,即使只有少数缩略图会同时显示。如果要减少使用的内存量,则必须延迟加载缩略图(即仅在需要时加载)。一种方法是存储文件位置而不是 BitmapImage,并根据需要加载图片。遗憾的是,您不能直接将路径绑定到 Image 控件(除非文件位于应用程序的本地存储中)。 In that question,我建议有类似问题的人使用自定义用户控件。

public sealed partial class LocalImage : UserControl
{
    public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof (string),
        typeof (LocalImage), new PropertyMetadata(null, SourceChanged));

    public LocalImage()
    {
        this.InitializeComponent();
    }

    public string Source
    {
        get { return this.GetValue(SourceProperty) as string; }
        set { this.SetValue(SourceProperty, value); }
    }

    private async static void SourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var control = (LocalImage)obj;

        var path = e.NewValue as string;

        if (string.IsNullOrEmpty(path))
        {
            control.Image.Source = null;
        }
        else
        {
            var file = await StorageFile.GetFileFromPathAsync(path);

            using (var fileStream = await file.OpenAsync(FileAccessMode.Read))
            {
                BitmapImage bitmapImage = new BitmapImage();
                await bitmapImage.SetSourceAsync(fileStream);
                control.Image.Source = bitmapImage;
            }
        }
    }
}