gif展示性能研究

背景

之前蜗牛在瀑布流展示数据的时候发现gif多,而且每个gif很大的时候内存会暴涨,索性研究了一下图片的加载过程,对比了SDWebImage和FLAnimatedImage 对gif的处理过程,做一个小结

知识背景

关于缓存:

当我们通过imageNamed:去读取一张本地的图片的时候,系统只是在Bundle那查找文件名,然后把这个文件放到UIImage里面返回,并没有做实际的文件读取和解码,当UIImage第一次显示到屏幕上时候,内部的解码方法才被调用,同时解码的结果会被保存待一个全局的缓存中去。在图片解码后,App第一次退到后台收到内存警告,该图片的缓存才会被清空。

当我们用imageWithData去读取一张图片的时候,UIImage底层是通过调用ImageIO的CGImageSourceCreateWithData方法去创建source对象的,这里的截图截自FLAnimatedImage,可以传一个shouldCache字段,默认条件下,这个参数是YES。所以用imageWithData时候也不能避免解码后图片的缓存,而解码发生的时机也是在图片第一次显示到屏幕上的时候。但是用这种方式解码数据是被缓存到CGImage内部,如果这个图片被释放,内部的解码数据也会被释放。

gif展示性能研究_第1张图片
01

如何避免缓存

手动调用 CGImageSourceCreateWithData() 来创建图片,并把 ShouldCacheShouldCacheImmediately 关掉。这么做会导致每次图片显示到屏幕时,解码方法都会被调用,造成很大的 CPU 占用。

如何提前解码

  1. 把图片用 CGContextDrawImage() 绘制到画布上,然后把画布的数据取出来当作图片。这也是常见的网络图片库的做法。解码消耗cpu资源
  2. 直接读取

1.CGImageSourceCreateWithData(data) 创建 ImageSource。

2.CGImageSourceCreateImageAtIndex(source) 创建一个未解码的 CGImage。

3.CGImageGetDataProvider(image) 获取这个图片的数据源。

4.CGDataProviderCopyData(provider) 从数据源获取直接解码的数据。
ImageIO 解码发生在最后一步,这样获得的数据是没有经过颜色类型转换的原生数据(比如灰度图像)。

SDWebImage对Gif的支持

无论图片是从disk寻找图片或者下载完成后sdwebImage都会调用sd_animatedWithData:如果是gif图片走gif的加载逻辑。

gif展示性能研究_第2张图片
02

gif加载逻辑,这里注意两点:

1.当图像被解码后,解码数据是会被缓存的,而且被缓存在CGImage中,生命周期和image一致。

2.到此得到的image还是没有发生解码。

gif展示性能研究_第3张图片
03

那么对于SDWebImage 中对于gif图的解码发生在什么时候呢,

decodedImageWithImage:在SDWebImage中一共有三处调用

1. 加载完成的时候,只对非gif图片有效,并且由shouldDecompressImages属性控制是否解码,默认为YES。

2. 从Disk读取image的时候,非gif都有效,且由shouldDecompressImages属性控制。默认为YES。

3. 在下载中的时候。因为需要做图片的渐变出现的效果,sdwebImage会在didReceiveData中对加了一部分的图片做解码并传递给业务方。对gif和非gif都有效,且由shouldDecompressImages属性控制。默认为YES。

所以在默认条件下,如果下载的资源为gif时候,在下载的过程中会对gif资源进行decode,在从disk读取gif后,也会做一次decode,由于读取gif都是用CGImageSourceCreateWithData(默认参数) 或者 imageWithData,所以decode后会带有缓存。且缓存的生命周期和image绑定。所以解释了,在有大gif的条件下,为何默认条件下进入feed流会引起内存飙高,但是CPU的的比较低。

decodedImageWithImage:内部做了判断,只解码非gif图片
默认条件下,所以默认条件下,gif的解码放到gif显示的时候,解码后的buffer会被系统cache,又由于SDWebImage带有cache缓存,会cache刚刚使用过的image,所以退出feed流页面后,还是内存还是居高不下,只有在手动清理webImage的cache后,内存才会下降。

SDWebImage对gif的处理

为什么SDWebImage对gif的处理效率低,而且对大的gif来说尤为明显。
我们知道一张图片从网络到显示,解码的过程必不可少,解码的过程必定需要消耗cpu资源,解码后必定会使得内存增大。如果提前解码缓存,cpu压力小,但是内存会高,如果不缓存,cpu压力大,内存使用少。这里的缓存包括代码的内存指定的内存缓存和ios系统对解码后的图片的缓存。

SDWebImage对gif的操作,即使关掉解码和存储到内存(sd里的memoryCache),解码数据还是会在展示的时候被系统缓存,所以性能不好。

FLAnimatedImageView对gif的处理

FLAnimatedImage 是由Flipboard开源的iOS平台上播放GIF动画的一个优秀解决方案,在内存占用和播放体验都有不错的表现。

FLAnimatedImageView的源码结构非常简单,FLAnimatedImage负责处理GIF,然后从缓存中提供给FLAnimatedImageView当前需要显示的图像

gif展示性能研究_第4张图片
04

关键方法解析

初始化

  1. 初始化缓存字典
  2. 初始化imageSource,根据 kCGImageSourceShouldCache 的官方文档描述, 所以设置 kCGImageSourceShouldCache为NO,可以避免系统对图片进行缓存,

Whether the image should be cached in a decoded form. The value of this key must be a CFBoolean value. The default value is kCFBooleanFalse in 32-bit, kCFBooleanTrue in 64-bit.

  1. 判断是否gif
  2. 取出gif播放次数
gif展示性能研究_第5张图片
05
  1. 遍历每帧图片
  2. 取出帧图片
  3. 取出的第一张图片为GIF动画的封面图片
  4. 取出帧图片的信息
  5. 取出帧图片的展示时间
gif展示性能研究_第6张图片
06
  1. GIF动画缓存策略
  2. 确认最佳的GIF动画的解码后帧图片缓存数量
gif展示性能研究_第7张图片
07

读取UIImage对象

  1. 对索引位置进行判断,避免出现越界情况
  2. 记录当前取出的帧图片的索引位置
  3. 判断GIF动画的帧图片的是否全部缓存下来了,因为有可能缓存策略是缓存所有的帧图片
  4. 根据缓存策略得到接下来需要缓存的帧图片索引,
  5. 除去已经缓存下来的帧图片索引
  6. 将需要缓存索引扔给其他线程进行解码装载,解码的过程是在其他线程,不会发生堵塞,解码也是通过CGContextDrawImage的方式进行解码。这个和SDWebImage一致。
  7. 取出帧图片
  8. 根据缓存策略清缓存


    gif展示性能研究_第8张图片
    08

gif播放部分

gif播放部分在startAnimating时候开开启CADisplayLink,进行重绘。

这里有两点需要注意,

  1. 每次CADisplayLink回调取的都是cache里的图片,如果cache里面没有图片,就跳过这次绘制机会

  2. 累加器的存在意义在与,避免反复去绘制同一帧

gif展示性能研究_第9张图片
09

所以FLAnimatedImageViewFLAnimatedImage是标准的生产者和消费者的模式。FLAnimatedImage开启一个子线程解码图片,FLAnimatedImageView消费图片进行展示。

总结

其实SDWebImageFLAnimatedImageView在gif的支持上,性能差别的主要原因有两个:

  1. FLAnimatedImageView存在一个缓存策略,每次也只解码一部分的帧数据,而且严格把控缓存数量。而SDWebImage依赖于系统在展示的时候的统一的全部帧解码,缓存的生命周期不好进行细粒度的控制。
  2. FLAnimatedImageView解码的过程放到了子线程中,而SDWebImage默认对gif不解码,所以解码发生在gif第一次显示时候,发生在主线程。

但是对于比较小且数量少的gif,其实两个性能差别不大,但是对于数量多或者gif本身比较大时候,性能差距会异常明显。

注意:最新的SDWebImage可以pod导入SDWebImage/GIF,对gif的处理自动支持了FLAnimatedImage

你可能感兴趣的:(gif展示性能研究)