【问题标题】:Image resize optimization causes memory usage to sky rocket图片调整大小优化导致内存使用飙升
【发布时间】:2013-03-06 16:18:52
【问题描述】:

我制作了一个函数,它获取原始图像并将其调整为 3 种不同的缩放比例 -> 16x、10x 和 4x。为了更好地理解,请继续阅读本段。假设原始图像是 1000x1000。我声明在 1 倍变焦时它的尺寸将为 50x50。这意味着 4 倍变焦将是 200x200,10 倍变焦将是 500x500,16 倍变焦将是 800x800。所以我的函数需要将原来的 1000x1000 缩小到 800x800,然后缩小到 500x500,然后缩小到 200x200。请注意,我已成功完成此操作,我的问题是关于内存使用情况

下面我有两种方法。两种方法都有效,但是一种方法会导致巨大的内存使用膨胀,使用的内存大约是另一种方法的 3 倍/4 倍……我更喜欢第二种方法,因为它的加载速度比第一种方法快得多,因为它不会调整 3 个图像中的每一个的大小从原始图像,而不是从以前调整大小的图像调整它们的大小。

注意:我正在使用 Xcode Instruments 来测量内存使用情况。 ImageResizer 类包含一个名为“Resize”的函数,用于调整图像的大小。

方法一)

    public List<UIImage> InitImageList_BFObjects ( UIImage image, SizeF frameSize )
    {
        List<UIImage> listOfImages = new List<UIImage>();

        for ( int i = 0; i < 3; i++ )
        {
            if ( i == 0 )
                zoomScale = 16f;
            else if ( i == 1 )
                zoomScale = 10f;
            else// if ( i == 2 )
                zoomScale = 4f;

            Resizer = new ImageResizer(image);
            Resizer.Resize(frameSize.Width * zoomScale, frameSize.Height * zoomScale);
            UIImage resizedImage = Resizer.ModifiedImage;
            listOfImages.Insert(0, resizedImage);

        }

        return listOfImages;
    }

方法 1 有效并且使用的内存非常少。我用一组大约 20 张图像运行了这个。加载后,我的应用使用了大约 14mb 的内存(使用 Xcodes Instruments 检查内存使用情况)

方法2。)

    public List<UIImage> InitImageList_BFObjects ( UIImage image, SizeF frameSize )
    {
        List<UIImage> listOfImages = new List<UIImage>();

        for ( int i = 0; i < 3; i++ )
        {
            if ( i == 0 )
                zoomScale = 16f;
            else if ( i == 1 )
                zoomScale = 10f;
            else// if ( i == 2 )
                zoomScale = 4f;


            if ( listOfImages.Count == 0 )
            {
                Resizer = new ImageResizer(image);
                Resizer.Resize(frameSize.Width * zoomScale, frameSize.Height * zoomScale);
                UIImage resizedImage = Resizer.ModifiedImage;
                listOfImages.Insert(0, resizedImage);
            }
            else
            {
                // THIS LINE CONTAINS THE MAIN DIFFERENCE BETWEEN METHOD 1 AND METHOD 2
                // Notice how it resizes from the most recent image from listOfImages rather than the original image
                Resizer = new ImageResizer(listOfImages[0]);
                Resizer.Resize(frameSize.Width * zoomScale, frameSize.Height * zoomScale);
                UIImage resizedImage = Resizer.ModifiedImage;
                listOfImages.Insert(0, resizedImage);
            }
        }

        return listOfImages;
    }

方法 2 有效,但内存使用量猛增!我用同一组大约 20 张图像运行了这个。加载后我的应用程序有超过 60mb 的内存使用量(使用 Xcodes Instruments 检查内存使用情况)为什么内存使用量如此之高?方法二到底是怎么回事,导致内存一飞冲天?这几乎就像一个变量没有被正确清理

* 附加信息,ImageResizer 类 **

我从 ImageResizer 类中删除了不需要的函数,并将其重命名为“ImageResizer_Abridged”。我什至转而使用这个类,以确保我没有不小心删掉任何需要的东西。

public class ImageResizer_Abridged
{
    UIImage originalImage  = null;
    UIImage modifiedImage = null;

    public ImageResizer_Abridged ( UIImage image )
    {
        this.originalImage = image;
        this.modifiedImage = image;
    }   

    /// <summary>
    /// strech resize
    /// </summary>
    public void Resize( float width, float height )
    {
        UIGraphics.BeginImageContext( new SizeF( width, height ) );
        //
        modifiedImage.Draw( new RectangleF( 0,0, width, height ) );
        modifiedImage = UIGraphics.GetImageFromCurrentImageContext();
        //
        UIGraphics.EndImageContext();
    }

    public UIImage OriginalImage 
    {
        get 
        {
            return this.originalImage;
        }
    }

    public UIImage ModifiedImage 
    {
        get 
        {
            return this.modifiedImage;
        }
    }
}

我创建了一个显示此问题的简化测试项目 *

这是项目的 Dropbox 链接:https://www.dropbox.com/s/4w7d87nn0aafph9/TestMemory.zip

这是方法 1 的 Xcode Instruments 屏幕截图作为证据(9 mb 内存使用): http://i88.photobucket.com/albums/k194/lampshade9909/AllImagesResizedFromOriginalImage_zps585228c6.jpg

这是 Method 2 的 Xcode Instruments 屏幕热作为证据(55 mb 内存使用): http://i88.photobucket.com/albums/k194/lampshade9909/SignificantIncreaseInMemoryUsage_zps19034bad.jpg

下面是运行测试项目所需的代码块

        // Initialize My List of Images
        ListOfImages = new List<UIImage>();

        for ( int i = 0; i < 30; i++ )
        {
            // Create a UIImage Containing my original Image
            UIImage originalImage = UIImage.FromFile ("b2Bomber.png");
            float newWidth = 100f;
            float newHeight = 40f;
            float zoomScale;
            float resizedWidth, resizedHeight;

            UIImage resizedImage1;
            UIImage resizedImage2;

            // Basically, I want to take the originalImage Image and resize it twice.  
            // Method 1.) Resize the originalImage and save it as ResizedImage1.  Resize the originalImage and save it as ResizedImage2.  We're finished!
            // Method 2.) Resize the originalImage and save it as ResizedImage1.  Resize ResizedImage1 and save it as ResizedImage2.  We're finished!

            // The pro to Method 1 is that we get the best possible quaility on all resized images.  The con is, this takes a long time if we're doing dozens of very large images
            // The pro to Method 2 is that it's faster than Method 1.  This is why I want to use Method 2, it's speed.  But it has a HUGE con, it's memory usage. 
            // Please run this project on an iPad connected to XCodes Instruments to monitor memory usage and see what I mean 

            zoomScale = 10f;
            resizedWidth = newWidth*zoomScale;
            resizedHeight = newHeight*zoomScale;
            UIGraphics.BeginImageContext( new SizeF( resizedWidth, resizedHeight ) );
            originalImage.Draw( new RectangleF( 0, 0, resizedWidth, resizedHeight ) );
            resizedImage1 = UIGraphics.GetImageFromCurrentImageContext();
            UIGraphics.EndImageContext();



            zoomScale = 4f;
            resizedWidth = newWidth*zoomScale;
            resizedHeight = newHeight*zoomScale;
            UIGraphics.BeginImageContext( new SizeF( resizedWidth, resizedHeight ) );

            // Run this project on an iPad and examine the memory usage in XCode's Instruments.  
            // The Real Memory Usage will be aroud 9 MB.  
            // Uncomment this "originalImage.Draw" line to see this happening, make sure to comment out the "resizedImage1.Draw" line

            // originalImage.Draw( new RectangleF( 0, 0, resizedWidth, resizedHeight ) );


            // Run this project on an iPad and examine the memory usage in XCode's Instruments.  
            // The Real Memory Usage will be aroud 55 MB!!  
            // My question is, why does the memory sky rocket when doing this, and how can I prevent the memory from sky rocketing??
            // My App requires me to resize around a hundred images and I want to be able to resize an already resized image (like in this example) without the memory usage sky rocketing like this...
            // Uncomment this "resizedImage1.Draw" line to see this happening, make sure to comment out the "originalImage.Draw" line

            resizedImage1.Draw( new RectangleF( 0, 0, resizedWidth, resizedHeight ) );
            resizedImage2 = UIGraphics.GetImageFromCurrentImageContext();
            UIGraphics.EndImageContext();


            // Add my resized images to the list of Images
            ListOfImages.Add (resizedImage1);
            ListOfImages.Add (resizedImage2);
        }

【问题讨论】:

  • 请注意,与直接从原始图像生成缩减相比,创建缩减的缩减几乎总是会产生更低的图像质量。
  • 我对此进行了测试,但在 iPad 上查看图像时,我无法“看到”差异。但我完全同意你的说法。
  • 如果没有看到您的 Resize 方法就很难回答 - 这可能是一个错误,但我怀疑是缓存问题。
  • 我添加了 ImageResizer_Abridged 类来向您展示 Resize 方法内部的详细信息。
  • 您的方法 2 将以 16x 创建第一张图像,然后以 160x(即 10*16)创建第二张图像(基于第一张图像)。你必须以原始图像为基础,或者做一些时髦的事情,比如zoomScale = 10f / 16f

标签: c# memory-leaks xamarin.ios uiimage


【解决方案1】:

我不确定您的Resize 代码,但我看到Scale 做了奇怪 的事情。一旦你深入研究它,这并不奇怪,但它绝对不明显。

创建UIImage 可以非常便宜,内存明智,只要不创建其支持CGImage。 IOW iOS 可能不会立即分配与新大小匹配的新 CGImage 支持图像。在需要 CGImage 之前,该分配将不同

在这种情况下,某些代码(如您的方法 1)可能在扩展时几乎不需要额外的内存。但是,您的第二种方法是使用按比例放大的图像(并且需要分配支持 CGImage 来执行此操作),因此它最终需要内存更早

如何检查?

将您的resizedImage.SizeresizedImage.CGImage.Size 进行比较。如果它们不匹配,那么您可能会遇到缓存。

备注

  • 我说可能,因为缓存逻辑是未知(未记录)。我知道这可能与在模拟器和设备上运行有所不同——而且它也因 iOS 版本而异;

  • 缓存是一件好事 - 但它可能会令人惊讶 :-) 我只是希望这被记录在案。

【讨论】:

  • 假设缓存是问题,无论如何我可以从调整大小的图像重新创建一个新的 UIImage 而无需创建支持 CGImage?当我尝试这样做时似乎很有趣,它带来了支持 CGImage 的东西(以及膨胀的内存使用......)我尝试做 UIIMage recreatedImage = new UIImage(resizedImage.CGImage);调整大小后,我确实检查了 resizedImage.Size 并将其与 resizedImage.CGImage.Size 进行了比较。他们出来是平等的......大小是否可能相同并且缓存仍然是问题? PS:我在上面添加了更详细的信息
【解决方案2】:

你检查过 Resizer 是否实现了 Dispose() 方法吗?我没有看到你把它扔到任何地方。

我相信您的新代码行正在对整个图像实施缩放,因此会增加内存使用量。

Resizer 正在以新的放大比例缩放整个图像,因此传入的 4MB 图像会缩放到 8MB、16MB 和 32MB,从而占用您的内存。

【讨论】:

  • ImageResizer 类没有 Dispose 方法实现。我尝试将几件事设置为 null 并调用 GC.Collect 但我尝试过的似乎没有帮助......也许我错过了一些东西
  • Pieter,你说的是“Resizer = new ImageResizer(listOfImages[0]);”这行吗?你是什​​么意思,实现整个图像的缩放?我在这里声明了一个 ImageResizer 类的新实例,并且只发送了 listOfImages 的第一个索引中的图像。那么它不应该只调整 listOfImages[0] 的大小吗?
  • 是的,但正在以新的放大比例缩放整个图像,因此传入的 4MB 图像会缩放到 8MB、16MB 和 32MB,会占用您的内存。
  • @Brian:扩展答案更合适吗?
【解决方案3】:

UIImage 实现了IDisposable,所以最终将不得不对它进行DisposeResize 方法似乎“丢失”了对 modifiedImage 的引用,所以我将使用 Dispose() 它。希望调用者对InitImageList_BFObjects 返回的列表中的所有图像执行相同的操作。或者 那个 类实现了IDisposable 并且它已经确定了谁必须处理它。但请放心,这些正在创建的图像需要在某个时候成为Dispose()d。

public class ImageResizer_Abridged
{
    private readonly UIImage originalImage;

    private UIImage modifiedImage;

    public ImageResizer_Abridged(UIImage image)
    {
        this.originalImage = image;
        this.modifiedImage = image;
    }

    /// <summary>
    /// stretch resize
    /// </summary>
    public void Resize(float width, float height)
    {
        UIGraphics.BeginImageContext(new SizeF(width, height));
        //
        var oldImage = this.modifiedImage;
        this.modifiedImage.Draw(new RectangleF(0, 0, width, height));
        this.modifiedImage = UIGraphics.GetImageFromCurrentImageContext();
        oldImage.Dispose();
        //
        UIGraphics.EndImageContext();
    }

    public UIImage OriginalImage
    {
        get
        {
            return this.originalImage;
        }
    }

    public UIImage ModifiedImage
    {
        get
        {
            return this.modifiedImage;
        }
    }
}

【讨论】:

  • 当我这样做时 ->> var oldImage = this.modifiedImage; ...... oldImage.Dispose();我的图像消失了。那是因为 oldImage 指向 this.modifiedImage 导致它处理 this.modifiedImage 吗?
  • 这是一个很好的问题。我只是稍微挖掘了一下以找到UIImage 是什么——我不做MonoTouch 开发。我想真正的问题是UIGraphics.GetImageFromCurrentImageContext() 是如何在后台运行的——它只是返回相同的this.modifiedImage 实例,还是完全创建一个新的UIImage?根据你的经验,我猜是前者。既然如此,请记住我给出的初始说明而不是代码。
猜你喜欢
  • 1970-01-01
  • 2016-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-23
  • 1970-01-01
相关资源
最近更新 更多