gif展示性能研究

背景

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

知识背景

关于缓存:

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

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

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的加载逻辑。

02

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

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

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

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当前需要显示的图像

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播放次数
05
  1. 遍历每帧图片
  2. 取出帧图片
  3. 取出的第一张图片为GIF动画的封面图片
  4. 取出帧图片的信息
  5. 取出帧图片的展示时间
06
  1. GIF动画缓存策略
  2. 确认最佳的GIF动画的解码后帧图片缓存数量
07

读取UIImage对象

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


    08

gif播放部分

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

这里有两点需要注意,

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

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

09

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

总结

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

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容