SDWebImage加载.gif 内存狂飙问题
于之前一直维护新浪博客,大量的东西都在这里,实在不想更换其他博客了,怎奈新浪对代码的排版,蛋疼至极,我尽量排版清晰些;
闲着没事,尝试用SD加载 .jif,在Cell里加载了大量的.jif,加载完成后意外出现了,内存狂飙到 700M+,滑动Cell会下降,大概到150M左右(所用的 .jif 本身比较大); 这个不能忍,于是各种解决:
在测试过程中发现SD 对混合图层的处理也不是很到位;不管是动态图还是静态图,都未做混合图层处理;
顺便提一下混合图层:
Color Copied Images:该选项可以给绘制时被Core Animation复制的图片添加蓝绿色叠加层
Color Misaligned Images:如果图片边界没有与目标像素完美对齐,该功能可为图片叠加上一层品红色。如果图
片使用确定的比例大小绘制,那么该功能会为图片添加一层黄色叠加。
原因概述: SD在对 .jif 的处理过程中采用了一个数组存储 jif 的帧图片,然而并没有及时释放;注意文中标注”
“的地方;
解决方案: 1. 采用YY_WebImage框架,
2. 在使用SDWebImage加载较多图片造成内存警告时,定期调用 [[SDImageCache sharedImageCache] setValue:nil forKey:@"memCache"]; 比如tableView加载更多的时候;
这里只介绍方案二:
1.首先分析SD加载 jif 的过程:
sd_animatedGIFNamed是SDWebImage提供的加载gif图片的一种方法。我们点进去这个方法去看以下。
sd_animatedGIFNamed 这个方法的实现如下。生成一个UIImage对象。
- (UIImage *)sd_animatedGIFNamed:(NSString *)name {
//取到屏幕分辨率
CGFloat scale = [UIScreen mainScreen].scale;
//是否是高清屏
if (scale > 1.0f) {
//如果是高清屏 取@2x图片 读取图片
NSString *retinaPath = [[NSBundle mainBundle] pathForResource:[name
stringByAppendingString:@"@2x"] ofType:@"gif"];
//图片转换为
data NSData *data = [NSData dataWithContentsOfFile:retinaPath];
//如果图片存在
if (data) {
//调用sd_animatedGIFWithData 生成image实例
return [UIImage sd_animatedGIFWithData:data]; }
//如果@2x图片不存在 读取普通图片
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"];
//图片转换为
data data = [NSData dataWithContentsOfFile:path];
//如果图片存在
if (data) {
//调用sd_animatedGIFWithData 生成image实例
return [UIImage sd_animatedGIFWithData:data]; }
//如果图片不存在
return [UIImage imageNamed:name]; }
else { //如果不是高清屏 读取普通图片
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"]; //图片转换为data
NSData *data = [NSData dataWithContentsOfFile:path];
//如果图片存在
if (data) {
//调用sd_animatedGIFWithData 生成image实例
return [UIImage sd_animatedGIFWithData:data]; }
//如果图片不存在
return [UIImage imageNamed:name]; } }
注释已经很详细了,这个类方法里面主要是确定当前设备的分辨率,以便加载不同分辨率的图片。
然后通过sd_animatedGIFWithData 后续处理
2.再来看看sd_animatedGIFWithData:
- (UIImage *)sd_animatedGIFWithData:(NSData *)data {
if (!data) {
return nil; }
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];}
else {
// 注意这里的数组:
NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0.0f;
for (size_t i = 0; i < count; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
duration += [self frameDurationAtIndex:i source:source];
// 数组不断的添加帧图片,然而并没有及时释放
[images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
CGImageRelease(image); }
if (!duration) {
duration = (1.0f / 10.0f) * count; }
animatedImage = [UIImage animatedImageWithImages:images duration:duration]; }
CFRelease(source); return animatedImage; }
先看这行代码
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
CGImageSourceRef定义如下,
typedef struct CGImageSource *CGImageSourceRef;
可以看到它是一个CGImageSource 指针。
CGImageSource是个什么东东呢?
CGImageSource是对图像数据读取任务的抽象,通过它可以获得图像对象、缩略图、图像的属性(包括Exif信息)。
那么这行代码可以这样理解:通过nadata取到图像的以系列信息。
再看size_t count = CGImageSourceGetCount(source);
这行代码是读取CGImageSourceRef有几个图片对象。
下面就不难理解了,
CGImageSourceCreateImageAtIndex :从source里面读取各个图片放入数组里面。
读取显示图片的 时间:
duration += [self frameDurationAtIndex:i source:source];
计算图片显示时间: - (float)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source { float frameDuration = 0.1f;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delayTimeUnclampedProp) {
frameDuration = [delayTimeUnclampedProp floatValue]; }
else {
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
if (delayTimeProp) {
frameDuration = [delayTimeProp floatValue]; } }
if (frameDuration < 0.011f) {
frameDuration = 0.100f; }
CFRelease(cfFrameProperties); return frameDuration;
}
最后:
从数组中读取帧图片并显示:
animatedImage = [UIImage animatedImageWithImages:images duration:duration];
播放数组里里面的图片。
从以上分析中可以知道: SD在处理 jif 的时候 采用数组暂存了 jif 的帧图片,并未及时释放,最终导致内存飙升问题;