美文网首页iOS开发攻城狮的集散地iOS开发好文iOS
使用SDWebImage下载高分辨率图,导致内存暴增的解决办法

使用SDWebImage下载高分辨率图,导致内存暴增的解决办法

作者: ocarol | 来源:发表于2016-07-25 13:06 被阅读11695次

最近,遇到一个问题,有个控制器,一进去就crash,而且手机非常的烫,用instrument跑了跑,发现内存暴增几百兆;如图:


Snip20160725_3.png

  图中可以看出,内存暴增的罪魁祸首是YYImage,再进一步定位问题,如图:


Snip20160725_6.png
  现在已经可以很清楚的知道,具体是哪些代码导致内存飙升的,这个方法“YYCGImageCreateDecodeCopy”,主要是对图像进行解压缩操作;同样的,换成SDWebImage,也出现了相同情况,由于某些原因,之后的分析都将以SDWebImage为例。
  先贴上调用的SDWebImage的代码:
[self sd_setImageWithURL:[NSURL URLWithString:imageUrl] placeholderImage:[UIImage imageNamed:@"defaulImage"] options:SDWebImageProgressiveDownload completed:nil];

instrument分析图:

Snip20160725_4.png
代码定位:
Snip20160725_8.png
  同样的,也是在解压缩的时候,出现内存飙升,但是为什么会这样呢?
  首先我想到“Create”必须得对应一个“Release”,于是我认真的看了每一行代码,无论是YYImage还是SDWebImage,都严格遵守了这一准则,既然都有Release,那么就不是内存泄露了,应该是在这几行代码执行的过程中,产生的内存消耗。可是怎么会消耗这么大呢?一张图片也就几M的大小啊,这个解压缩存在的意义是什么呢?
  我从“H_伟华 博乐家园”的一篇博客中找到了解压缩存在的意义(http://blog.163.com/huang1988519@126/blog/static/737875752013101803137445/)
当完成图片加载或者从本地加载图片时,还会有轻微的卡顿。
因为当显示或者绘制的时候,UIKit 只做了额外的延迟初始化和消耗很高解码。
而下面的代码片段,从后台线程解压缩成合适的格式,从而让系统不必做额外的转换。
然后在主线程上显示

我又疑惑了,既然是为了优化,为啥会适得其反呢?我百思不得其解,最后在SDWebImage的issues找到了相关的讨论:
https://github.com/rs/SDWebImage/issues/538
其中一个harishkashyap大神是这么回答的:

harishkashyap commented on Dec 23, 2014
Its the memory issue again. decodedImageWithImage takes up huge memory and causes the app to crash. I have added an option to put this off in the library but defaulting to YES so there aren't any breaking changes. If you put off the decodeImageWithImage method in both image cache and image downloader then you shouldn't be seeing the VM: CG Raster data on the top consuming lots of memory

decodeImageWithImage is supposed to decompress images and cache them so the loading on tableviews/collectionviews become better. However, with large set of images being loaded, the experience worsened and the memory of uncompressed images even with thumbnails can consume GBs of memory. Putting this off only improved performance.

[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];

https://github.com/harishkashyap/SDWebImage/tree/fix-memory-issues

这位大神提到,decodeImageWithImage这个方法用于对图片进行解压缩并且缓存起来,以保证tableviews/collectionviews 交互更加流畅,但是如果是加载高分辨率图片的话,会适得其反,有可能造成上G的内存消耗。该大神建议,对于高分辨率的图片,应该在图片解压缩后,禁止缓存解压缩后的数据,相关的代码处理为:

[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];

虽然这位大神给了肯定的答案,但是为什么会如此呢?
我查阅了apple官方文档对CGBitmapContextCreate函数的注解:


Snip20160725_10.png

图中红框部分的参数,引起了我的注意:

bitsPerComponent 表示存入内存中的每个像素中的每一个组件所占的位数;
bytesPerRow 表示存入内存中的位图的每一行所占的字节数;

我猜测,解压缩操作中,每一个像素点都会分配一个空间来存储相关值,那么分辨率越高的图片,就意味着更多数量的像素点,也就意味着需要分配更多的空间!所以对于高分辨率图来说,如果缓存解压缩之后的数据,即使是几M的图片,也是有可能消耗上G的内存!
  既然如此,我决定按照harishkashyap大神的方法,直接让下载高分辨率图的地方,避免缓存解压缩后的数据操作!
  项目中,高清图涉及到的地方,都全部已经封装起来了,那么就轻松了很多。为了保证封装类不对外界产生影响,我只在调用封装类时,禁用缓存解压缩数据,调用完毕再恢复原设置即可。这样既能保证高分辨率图不crash,也能保证其他地方,普通图片依旧可以通过缓存解压缩数据进行优化。
  由于封装的是一个控制器,所以我决定在控制器loadView方法中禁用解压缩,在delloc方法中恢复原设置:
1、首先在封装的控制器中定义变量用于存储原设置:

static BOOL SDImageCacheOldShouldDecompressImages = YES;
static BOOL SDImagedownloderOldShouldDecompressImages = YES;

2、loadView中保存原设置并且禁用缓存解压缩数据:

SDImageCache *canche = [SDImageCache sharedImageCache];
SDImageCacheOldShouldDecompressImages = canche.shouldDecompressImages;
canche.shouldDecompressImages = NO;

SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader];
SDImagedownloderOldShouldDecompressImages = downloder.shouldDecompressImages;
downloder.shouldDecompressImages = NO;

3、dealloc中恢复原设置:

-(void)dealloc {
    SDImageCache *canche = [SDImageCache sharedImageCache];
    canche.shouldDecompressImages = SDImageCacheOldShouldDecompressImages;
    
    SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader];
    downloder.shouldDecompressImages = SDImagedownloderOldShouldDecompressImages;
}

再次用instrument跑了一下,方法果然有效,内存彻底降下来了,如图:


Snip20160725_13.png

当然,你也可以设置SDWebImage的其他参数,比如是否缓存到内存以及内存缓存最高限制等,来保证内存安全:

shouldCacheImagesInMemory 是否缓存到内存
maxMemoryCost  内存缓存最高限制

号外:苹果官方给出了一个下载高清大图的demo,内存消耗很低。感兴趣的朋友也可以看看:
https://developer.apple.com/library/ios/samplecode/LargeImageDownsizing/Introduction/Intro.html

相关文章

网友评论

  • 遛遛食:为什么我找不到这个属性啊?
    shouldDecompressImages
    Hunter琼:@遛遛食 我在appdelegate 默认设置为NO 有影响吗??
    遛遛食:@Hunter琼 不是,SD加载的一直都是原图
    Hunter琼:我也遇到了 是SD太老么??
  • 起个名字好难O0:试过了, 不管用啊. 该涨还是涨啊
  • willokyes:亲爱的楼主,你这篇文章有一点误导了不少人即“禁止解压缩”,禁止解压缩是不可能的,一辈子都不可能的。因为图片无论是jpg还是png都要解压缩成位图bmp,才能渲染到屏幕上,无论使用第三方库还是原生方法解压缩高像素图片的一瞬间都会消耗很多内存,解压缩这一步是逃避不了的,应该这么讲:解压缩图片后,重要是要禁止缓存解压缩后的图片才能恢复消耗的内存。

    可参考大神文章:http://blog.leichunfeng.com/blog/2017/02/20/talking-about-the-decompression-of-the-image-in-ios/
    ocarol:感谢您的评价和指正,已经对文章说法有误的地方进了修正!再次感谢!
  • 柯索:现在YY可以挺起身板了
    遛遛食:大神YY的也有问题怎么办?
  • AppleIdGX:我的tableView中每个cell上才1张图片,每张图片大概480*300像素(算下来的话,图片最大才500k,不大啊)。6sp上测试,当tableView的cell达到1000条时,内存可达到400m。
    使用insruments查看,的确有一部分内存的增加是解压图片引起的,依照本文和issue里面的回答,我直接将源码里面shouldDecompressImages的默认全部改成了NO,重新测试后,发现还是可以飙到300m。
    基本可以猜测到都是sd将图片缓存到内存而又没有及时清理引起的。然后我就将shouldCacheImagesInMemory这个属性设置成了NO,不将图片存在内存,内存果然就稳定在了80m左右。缓存的图片从disk里取,没有感受速度降慢。
    不过毕竟感觉不是很友好,还是应该限制存储到内存的图片空间好一点。确认有效的属性是maxMemoryCost,设置为1024*1024*10(像素为单位,每个像素4个字节)。最后内存稳定在110m以内,可以接受。
    如果有谁跟我的情况类似,可以参考下。
    起个名字好难O0:@topws1 我也差不多. SD占了 45%, main 占了 49.9%. 而且用文章的方法, 还是不行. 内存一直在四百左右
    AppleIdGX:@topws1 木有。。。
    topws1:有没有遇到这些配置都写了,instruments检测时SD确实没有占用内存了,但是main 函数却占用了大量的内存,导致内存还是会彪上去,main函数也没法准确的定位到具体哪里占了内存
  • wdxgtsh:棒棒哒~~~~ :clap:
  • 东东隆东抢:我也遇到这样的问题,但是按照本文的方法不能够解决问题。
    tableview加载大量图片时,上下滑动cell时,内存还是会暴增。
    不懂后悔:@东东隆东抢 我是在收到内存警告时清理。但是貌似没用。
    东东隆东抢:@不懂后悔 当时处理方式很暴力,就是直接清理缓存内容,内存暴增问题解决了,但是好像产生了一些其他问题。
    不懂后悔:@东东隆东抢 你好,你的内存问题解决了么。我现在我也是这个问题。内存居高不下
  • 科比布莱恩特:首先感谢您分享这么好的文章,本人亲测目前github最新版本的SDWebImage
    这个第三方库,在解压缩这张超级大分辨率图片的时候,不会崩溃,我在自己的控制器里面 没有设置您说的[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
    [[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];
    图片使用的是这张
    http://www.onegreen.net/maps/Upload_maps/201412/2014120107280906.jpg
    通过xcode断点调试进入decodedAndScaledDownImageWithImage这个解压缩方法不会崩溃。
    起个名字好难O0:SDImageCache *canche = [SDImageCache sharedImageCache];
    canche.config.shouldDecompressImages = SDImageCacheOldShouldDecompressImages;
    不知道是不是这样写. 这样可以获取到. 但是一点作用都没有
  • adcbcf0b9029:https://pan.baidu.com/s/1dFaMwCD 楼主你试试这个图片 ,分辨率很大 ,内存飙升,
    ocarol:链接失效了
    ocarol:不好意思,最近太忙了,我这周抽时间看下你说的这个图片
  • adcbcf0b9029:楼主啊 我怎么试了不管用啊 还是高
    ocarol:@adcbcf0b9029 YY没用,SD有用
  • 羊肉泡馍啊:好厉害
  • IOSMan:我在SE,10.2系统禁止解压缩可以降低内存;但是在6S,9.3系统禁止解压缩没有效果。
    [[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
    [[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];
    [self.imageView sd_setImageWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:@"large_leaves_70mp.jpg" ofType:nil]]];
    AppleIdGX:@ocarol 有修复吗? 怎么我的还可以上升到3,400m。程序退出到后台就降回几十m了。直接把4.0源码里面所有的shouldDecompressImages改为NO了,还是没用
    ocarol:@IOSMan 这篇文章其实是比较老了,有朋友说SD最新版已经修复了这个问题,所以本篇也就没有什么价值啦:smile:
    IOSMan:和系统版本没关系,我用5S,10.2.1系统不能降低内存,用7P,10.2.1可以降低内存,和机型有关。另外最新的SD4.0.0没有了[[SDImageCache sharedImageCache] setShouldDecompressImages:NO]方法,只有[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO]; ,但只用这一个方法在SE和7还是有效降低内存。
    另外苹果官方给的例子可以有效降低内存,但是加载时间太长了
  • 君莫叹人生如若初见:博主你好,看完你这篇文章我还有一点疑惑:如果高清分辨图关掉解压缩,那么解压缩这个存在岂不是变得没有意义了,解压缩的存在就是为了优化内存而使用的,那么这样加载出来的高清图如果很多,不是会更卡顿吗?还望博主提点。
    ocarol:@君莫叹人生如若初见 理论上的确会有一些影响,但是当时我使用的demo加载将近500张高清图,并没有出现明显的卡顿现象,
  • keshiim:我想说的是,最终下载下来的图片还是要解码,然后移交给GPU去渲染,解码的过程可以是系统默认的,但是还是很吃内存😅
    ocarol:@keshiim 多谢提醒:smile:
    keshiim:@ocarol 现在sd新版本已经解决了这个问题
    ocarol:是的
  • SawyerZh:请教下楼主:pray:instrument 显示内存的代码定位 的开启步骤是怎样的
    SawyerZh:@ocarol 好的 非常感谢
    ocarol:@最帅的人 可以看下如何使用instrument,http://www.jianshu.com/p/92cd90e65d4c
  • 小微向前冲:分析的很透彻,很到位!
  • chenyu1520:什么样的图片才能算是高清图?我的 APP 中大部分是40-80kb 的,也有300-400kb 的,不过用了那种方式,都会导致内存增大,不过现在基本在150M 左右,这个值正常吗?一般 APP 占用多打的内存空间才算是正常呢?
    ocarol:@chenyu1520 内存在150M左右不算正常了,我这个例子,之前是保持在200M左右都会闪退,个别图片会飙到500M,优化后内存在30M上下,我们的app是要求不超过100M
  • 帆动世界:高清图的这个问题也遇到过,长知识了
  • ios122: :+1: 曾经遇到过这个问题,没有深入分析过.... LZ,解答了我的困惑
  • 狗娃_:之前也有这样的情况,确实是因为高清图引起的,后来是让后台对图片进行了处理才解决。没有像楼主这样去解决问题,惭愧,像楼主学习!
    ocarol:@阿牛哥_ :joy:
  • 巴图鲁:不错
  • BinBear:楼主有尝试解决YYImage的问题吗
    ocarol:@BinBear 之前没有好好试过YY,你说了之后我又去尝试了一下,发现YY很多地方直接就是解压缩操作,比如 YYCGImageCreateDecodedCopy(imageRef, YES);,目前发现YY是否解压缩在很多地方不可控,等有些时间的时候,再好好研究下YY,如果用高清图,暂建议用SD,当时之所以是以SD为分析目标,是因为当时需要支持IPV6,SD已经支持IPV6,而YY还是用的被废弃的NSURLConnection,故而采用的SD,标题有点歧义,也是自己考虑不周
  • 6fdb0c58ceca:我用instrument从来没有定位代码成功过, 都是乱七八糟的不知所以的东西
  • dc5995566176:哎呀妈呀! 偶像! 膜拜!!!!
  • 蚂蚁牙齿不黑:正在想如何下载高清图的问题,不错
  • 子达如何:库应该自己自适应,发现图太大就不应该解压缩
  • WoodenSouthRock:这是面试常问到的
    ocarol:@CNHanyan :+1:
  • 桐生一猫:我以前用了,没效果。用户在网站上上传的图片20M一张,刷了一会就内存警告,闪退了
  • 零点知晨:哈哈,遇到过这个问题。感谢楼主分享
  • Lv丶:我以前的做法是进入sdwebimage内部代码,在图片写入之前自己本地写一个低分辨率的图片覆盖上去,尽量弱化图片的分辨率使内存占用减少
    ocarol:@Lv丶 :+1:,有时候需求就是必须是高清图
  • fd1d725aafc7:我想问一下作者,对于YYImage 这个问题应该怎么处理, 我没找到相应的禁止解压缩的方法。
    fd1d725aafc7:@ocarol 谢谢,那我也准备不用YY了,还是换成用SD吧。
    ocarol:@阿莫西林miku 后来我尝试了YY,发现单是这样设置,没有什么效果,YY的很多函数,都会通过解压缩的步骤,然后有些函数中,对于是否解压缩直接就是传递的YES,暂时我也没有得出特别好的解决方案,等得空的时候,再好好研究下YY。
    ocarol:@阿莫西林mike 使用YY从网络下载图片的时候,要传递一个参数YYWebImageOptions,选择YYWebImageOptionIgnoreImageDecoding,从缓存取的时候,[YYImageCache sharedCache]中设置decodeForDisplay = NO;具体的我并没有完全尝试,你可以试下
  • 没有黑眼圈de熊猫:我也遇到了这种问题,但是和楼主的好像还不大一样,试了楼主的方法,好像没有解决,不知道是什么原因
    何康老鬼:厉害了:+1:
    ocarol:@普罗旺斯_minmin 我的这个是下载高分辨图时产生的内存暴增,你可以用instrument定位下,看看问题出在哪.
  • 南烟客777:急求得,参考下,谢谢撸主。。。。
  • 赤洱:碰到同样问题了,但是只知道是因为高清图片引起的,但是没有分析到原因,感谢楼主,学习了。
  • bde04638cca8:感谢分享 这点是我们经常忽略的
  • xxttw: 感谢分享 收藏备用
  • 汾酒iOSer:感谢分享!:clap::clap::clap:
  • Caiflower:感谢分享
  • 44d3387e09f3:感谢分享
  • 我系哆啦:内存暴增,一个可以用自动释放池优化,另外,考虑让后台搞一个图片服务器,需要什么分辨率的图片,直接从后台获取,或者前端也可以自己压缩,但是要考虑到内存和cpu的消耗,一般自动释放池加同步串行对接,一次处理一张图
    YungFan:@我系哆啦 这种方式比较好
    我系哆啦:@我系哆啦 我擦,队列怎么总打成对接……
    我系哆啦:@我系哆啦 新建同步串行对接后台子线程
  • Jerry在种草:感谢分享
  • Mr_墨:!
  • 爱依然:好棒
    ocarol:@爱依然 :smile:

本文标题:使用SDWebImage下载高分辨率图,导致内存暴增的解决办法

本文链接:https://www.haomeiwen.com/subject/qmpgjttx.html