SDWebImage加载.gif 内存狂飙问题

作者: 不会打滚儿的狮子 | 来源:发表于2016-08-10 01:09 被阅读2668次

    于之前一直维护新浪博客,大量的东西都在这里,实在不想更换其他博客了,怎奈新浪对代码的排版,蛋疼至极,我尽量排版清晰些;

    闲着没事,尝试用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 的帧图片,并未及时释放,最终导致内存飙升问题;

    相关文章

      网友评论

      • 480663d4d4d5:这个相当于没说啊 - 如果加在一个帧数很多的gif , iamgeview.image 会保持这个gif 的UImage 的所有帧的图片,而且还是解码过的,我们应该关注的点是这个
      • 小赢一场:看sd_animatedGIFWithData代码这个方法执行完毕就释放了图片帧数组的,而且在循环的时候已经对image做了CFRelease释放,应该不是没释放的问题
      • zzsunboy:你好,既然SD在处理 jif 的时候 采用数组暂存了 jif 的帧图片,并未及时释放,最终导致内存飙升问题;那这个问题怎么处理呢?
        zzsunboy:不用的
        smkoc:其实我也想知道,是不是要[[SDImageCache sharedImageCache] setValue:nil forKey:@"memCache"]

      本文标题:SDWebImage加载.gif 内存狂飙问题

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