使用多张图片做帧动画的性能优化

背景

QQ群的送礼物功能需要加载几十张图然后做帧动画,但是多张图片加载造成了非常大的性能开销,导致图片开始加载到真正播放动画的时间间隔比较长。所以需要研究一些优化方案提升加载图片和帧动画的性能。

原理分析

iOS系统从磁盘加载一张图片,使用UIImageView显示到屏幕上,需要经过以下步骤:

从磁盘拷贝图片数据到内核缓冲区。

从内核缓冲区复制数据到用户空间。

生成UIImageView,把图像数据赋值给UIImageView。

如果图像数据为未解码的PNG/JPG,解码为位图数据。

CATransaction捕获到UIImageView layer树的变化,主线程Runloop提交CATransaction,开始进行图像渲染。如果数据没有字节对齐,Core Animation会再拷贝一份数据,进行字节对齐。GPU处理位图数据,进行渲染。

加载图片

加载图片和图片解码是比较容易影响性能的一些因素。iOS设备上的闪存虽然是非常快的,但是还是要比内存慢接近200倍左右。图片加载的性能取决于CPU和IO,所以适当的减少IO次数可以提升一部分性能。

通常情况下,应用应该在用户不会察觉的时候加载图片,可以针对情况采用预加载图片或者延迟加载。如果是帧动画这种情况,图片很难做延迟加载,因为帧动画的时间比较短,延迟加载很难保证播放每一帧时能提前加载到图片。有些比如列表滑动之类的情况延迟加载就会比较合理,并且可以采用子线程异步之类的方法防止滑动卡顿。

解码

图片加载结束之后在被渲染到屏幕之前,如果是未解码的JPEG或者PNG格式,图片会先被解码为位图数据。经过实际的测试,图片解码通常要比图片加载耗费更多的时间。iOS默认会在主线程对图像进行解码。很多库都解决了图像解码的问题,不过由于解码后的图像太大,一般不会缓存到磁盘,SDWebImage的做法是把解码操作从主线程移到子线程,让耗时的解码操作不占用主线程的时间。

解码与加载图片耗时对比

测试机型:iPhone 6+

---

只加载不解码平均时长

加载和解码平均时长

同一张图加载三十次

0.000858531

0.005955906

加载三十张不同的图

0.002828871

0.015458194

解码的时间通常是加载图像数据时间的三到四倍左右。

各种加载图片API的解码的时机

UIKit:

+imageNamed://加载到原图后立刻进行解码

+imageWithContentsOfFile://图像渲染前进行解码

UIImageView的image被赋值时会立刻进行解码。

图像用UIKit内的绘图API绘制时会立刻进行解码,这个API的好处是可以在子线程进行。

ImageIO:

NSURL *imageURL = [NSURL fileURLWithPath:str];

NSDictionary *options = @{(__bridge id)kCGImageSourceShouldCache: @YES, (__bridge id)kCGImageSourceShouldCacheImmediately: @NO};

CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL);

CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,(__bridge CFDictionaryRef)options);

UIImage *image = [UIImage imageWithCGImage:imageRef];

CGImageRelease(imageRef);

CFRelease(source);

kCGImageSourceShouldCacheImmediately 决定是否会在加载完后立刻开始解码。

缓存

缓存分为原图像的缓存和解码后位图数据的缓存。

一般情况下,解码后的位图数据的缓存会跟随着原图UIImage,并且UIImage被拷贝后,指针变了之后图像需要重新去解码。

解码后的位图数据可以通过以下的API获取。

1.CGImageSourceCreateWithData(data) 创建 ImageSource。

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

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

4.CGDataProviderCopyData(provider) 从数据源获取直接解码的数据。

各种加载图片API的缓存策略

UIKit:

+imageNamed://原图和解码后的位图数据都会保存在系统缓存下,只有在内存低之类的时候才会被释放。所以这个API适合在应用多次使用的图片上使用。

+imageWithContentsOfFile://不会对原图做缓存,只用一次的图片应该使用这个API。

ImageIO:

ImageIO的API的kCGImageSourceShouldCache选项决定了是否会对解码后的位图数据做缓存。64位设备上默认为开,32位设备上默认为关。

任何时候,选取如何缓存总是一件比较难的事,正如菲尔 卡尔顿曾经说过:“在计算机科学中只有两件难事:缓存和命名”。

解码后位图数据的缓存不好控制,我们一般也不会去操作它,但是原图像还是可以做一些缓存的。除了使用系统API来做缓存以外,应用也可以自定义一些缓存策略。或者可以使用NSCache。

NSCache的API和NSDictionary很像,并且会根据所保存数据的使用频率,内存占用情况会适时的释放掉一其中一部分数据。

图片格式

常用的图片格式一般分为PNG和JPEG。

对于PNG图片来说,加载会比JPEG更长,因为文件可能更大,但是解码会相对较快,而且Xcode会把PNG图片进行解码优化之后引入工程。JPEG图片更小,加载更快,但是解压的步骤要消耗更长的时间,因为JPEG解压算法比基于zip的PNG算法更加复杂。

JPEG 对于噪点大的图片效果比较好,PNG适合锋利的线条或者渐变色的图片。对于不友好的png图片,相同像素的JPEG图片总是比PNG加载更快,除非一些非常小的图片、但对于友好的PNG图片,一些中大尺寸的图效果还是很好的

本文测试时使用的所有图片均为PNG格式。

渲染

关于图像的渲染,主要从以下三点分析:

offscreen rendring

Blending

Rasterize

Offscreen rendering指的是在图像在绘制到当前屏幕前,需要先进行一次渲染,之后才绘制到当前屏幕。在进行offscreen rendring的时候,显卡需要另外alloc一块内存来进行渲染,渲染完毕后在绘制到当前屏幕,而且对于显卡来说,onscreen到offscreen的上下文环境切换是非常昂贵的(涉及到OpenGL的pipelines和barrier等),

会造成offscreen rendring的操作有:

layer.mask 的使用

layer.maskToBounds 的使用

layer.allowsGroupOpacity 设置为yes 和 layer.opacity 小于1.0

layer.shouldRasterize 设置为yes

layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing

Blending 会导致性能的损失。在iOS的图形处理中,Blending主要指的是混合像素颜色的计算。最直观的例子就是,我们把两个图层叠加在一起,如果第一个图层的透明的,则最终像素的颜色计算需要将第二个图层也考虑进来。这一过程即为Blending。更多的计算,导致性能的损失,在一些不需要透明度的地方,可以设置alpha为1.0 或者减少图层的叠加。

Rasterize启用shouldRasterize属性会将图层绘制到一个屏幕之外的图像。然后这个图像将会被缓存起来并绘制到实际图层的contents和子图层。如果有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧划得来得多。但是光栅化原始图像需要时间,而且还会消耗额外的内存。

当我们使用得当时,光栅化可以提供很大的性能优势但是一定要避免作用在内容不断变动的图层上,否则它缓存方面的好处就会消失,而且会让性能变的更糟。

优化点

加载

应用可以通过减少IO次数来优化图像加载性能。这时候就可以使用精灵序列来把多张小图合成一张大图。 这样就能极大的减少IO次数了。

因为IOS设备的限制,大图的大小不能超过2048 2048,有部分设备上这个限制可以达到4096 4096,但我们这里以小的为准。所以一张大图内最多只能容纳10张测试用的小图。

精灵序列这种技术在cocos2d-x等平台上使用的很多。把多张小图拼成一张大图时可以使用TexturePacker软件来完成。

精灵序列

简单来说精灵序列就是把多张小图拼成一张大图,附加一份plist文件保存着每一张小图对应在大图上的位置。然后iOS上就可以通过如下代码在不同小图之间切换。

layer.contents = (id)img_.CGImage;//img为对应的大图

layer.contentsRect = CGRectMake(0.1, 0.1, 0.2, 0.2);//contentsRect就是对应大图上小图的位置,更改这个值就可以在不同的小图间切换了。

精灵序列与普通帧动画的性能对比

下面的性能对比是通过20张小图和小图拼接而成的2张大图完成一组动画的对比数据。 测试机型是iPhone4s.

1.文件大小

小图总大小

大图总大小

758K

827K

因为大图内很难平铺所有小图,所以大图内很容易有空白像素,这就导致拼接后大图的总分辨率很容易超过所有小图的总分辨率,也就造成大图的体积会比所有小图的体积大。但是也有大图的体积小于所有小图总体积的情况,因为TexturePacker在拼接的时候会把小图内空白的像素适当的做点裁剪,然后把这个偏移值保存在plist文件内。

2.加载速度

小图的加载时间

大图的加载时间

≈25ms

≈5ms

一张大图能容纳10张小图,极大的减小了IO数量,所以加载速度能大大提升。

3.解码时间

小图的解码时间

大图的解码时间

≈35ms

≈40ms

因为大图的数据量比较大,所以大图的总体解码时间要比小图的解码时间长。

4.CPU占用(CPU占用是通过同时执行两种动画,然后分别计算出两种动画的CPU占用率)

小图方案的CPU占用率

大图方案的CPU占用率

≈8% 峰值比较高

≈20% 峰值比较低

占用CPU最多的一个是从本地加载数据,另一个是图片解码。因为大图的IO次数比较少,所以加载大图时的CPU占用率要比加载所有小图时的低,但是大图的解码和两张大图切换时比较耗费CPU。总体来说大图这种方案的CPU占用率要高一点。

5.内存占用

小图方案的内存占用

大图方案的内存占用

≈27MB

≈30MB

6.帧率

小图方案的帧率

大图方案的帧率

≈7fps

≈7fps

大图方案和小图方案的帧率基本一致。

精灵序列总结

总体来说精灵序列这种方案能明显减小图片开始加载到动画开始播放的延迟。但是精灵序列的文件大小容易变大,并且CPU占用率也要高一点。

--

文件大小

加载速度

解码时间

CPU占用

内存占用

通过大图实现的精灵序列

通过小图实现的普通帧动画

精灵序列这种方案或许也可以直接用解码后的数据作为原图数据,这样虽然会让内存占用更高,文件大小进一步加大,但是能降低解码时间和CPU占用率。这个可以作为一个优化点继续研究一下。

精灵序列的风险点:

因为一张大图最多能容纳10张小图,所以在两张大图切换的时候会造成CPU占用变高,这样就会有造成卡顿的风险。但是在iPhone4s上的测试下基本没有出现过卡顿。

拼接后的大图的文件大小容易变大,如果大图通过网络下载时耗时会变长。

延迟解码

根据上文的原理分析,针对多图帧动画,应用可以将图片解码延迟到图片渲染前,不要让图片加载后立马开始解码。这样也能降低图片加载到动画开始播放的延迟。

缓存

因为QQ群的送礼物功能中同一副帧动画重复播放的频率并不高,所以不需要考虑对原图或者对解码后位图数据做缓存。

渲染

渲染图形方面应用只能在将需要绘制的内容提交给GPU前做一些优化。针对帧动画这种场景,我们可以通过保证图像素材做到像素对其,尽量减少透明像素来做一些优化。

如果使用精灵序列来做帧动画,应用必须通过CALayer进行渲染。播放帧动画时可以用NSTimer也可以使用CADisplayLink来不断的刷新图像数据。

如果不使用精灵序列,应用也可以通过自定义一个UIImageView,自己通过CADisplayLink或者NSTimer实现帧动画,这样的话应用就可以自由控制图像解码的时间,图像数据的缓存等。

总结

本文通过对图片加载,多图做帧动画进行了一些原理分析,并且给出了一些优化点。也简单介绍了一下用精灵序列做帧动画的方案。除了QQ群送礼物的功能外其他通过图片做帧动画的功能也可以针对具体的业务情况选取其中的一些优化点进行一些优化。

更多精彩内容欢迎关注腾讯优测的微信公众账号:

腾讯优测是专业的移动云测试平台,为应用、游戏,H5混合应用的研发团队提供产品质量检测与问题解决服务。不仅在线上平台提供「全面兼容测试」、「云手机」等多种质量检测工具,同时在线下为VIP客户配备专家团队,提供定制化综合测试解决方案。真机实验室配备上千款手机,覆盖亿级用户,7*24小时在线运行,为各类测试工具提供支持。

java如何运用多帧图片_【腾讯优测干货分享】使用多张图片做帧动画的性能优化...相关推荐

  1. 【腾讯优测干货分享】使用多张图片做帧动画的性能优化

    本文来自于Dev Club 开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57fc8cea302e4725036142f6 使用多张图片做帧动画的性能优 ...

  2. 【腾讯优测干货分享】使用多张图片做帧动画的性能优化 1

    本文来自于Dev Club 开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57fc8... 使用多张图片做帧动画的性能优化 背景 QQ群的送礼物功能需要 ...

  3. java中文件处理之图片_在Java 7中处理文件

    java中文件处理之图片 以下是The Well-Grounded Java Developer的草稿的修改后的片段. 它使您快速了解与以前版本相比,在Java 7中操作文件要容易得多. 通过使用新的 ...

  4. java中为按钮添加图片_我们可以在Java接口中为成员定义私有和受保护的修饰符吗?...

    java中为按钮添加图片 No, it is not possible to define private and protected modifiers for the members in int ...

  5. java中为按钮添加图片_如何在Java中为字符串添加双引号?

    java中为按钮添加图片 In Java, everything written in double-quotes is considered a string and the text writte ...

  6. 使用多张图片做帧动画的性能优化

    背景 QQ群的送礼物功能需要加载几十张图然后做帧动画,但是多张图片加载造成了非常大的性能开销,导致图片开始加载到真正播放动画的时间间隔比较长.所以需要研究一些优化方案提升加载图片和帧动画的性能. 原理 ...

  7. java中把gui插入图片_在java中使用图片实现gui的美化.pdf

    在java中使用图片实现gui的美化.pdf 还剩 3页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,喜欢就下载吧,价低环保! 内容要点: 图形图像处理GRAPHICS AND IMAGE ...

  8. java前端接收回显图片_图片上传并回显后端篇

    图片上传并回显后端篇 我们先看一下效果 继上一篇的图片上传和回显,我们来实战一下图片上传的整个过程,今天我们将打通前后端,我们来真实的了解一下,我们上传的文件,是以什么样的形式上传到服务器,难道也是一 ...

  9. java 向 mysql数据库存储图片_基于java向mysql数据库中存取图片

    import java.io.*; import java.sql.*; import java.sql.DriverManager; import java.sql.ResultSet; impor ...

最新文章

  1. mysql dump 到的文件_mysql查询结果导出到文件
  2. 80年代高考数学卷,共10套,有您做过的高考卷吗?
  3. hive mysql 远程_Hive配置 远程连接MySQL
  4. 2016年印度光伏设备市场将猛增2倍达4GW以上
  5. android自定义抽奖,Android自定义view制作抽奖转盘
  6. 斩获2019 Thales AIChallenge4Health第一,腾讯优图医疗AI再获突破
  7. P3793-由乃救爷爷【分块,ST表】
  8. python sql查询返回记录_干货!Python与MySQL数据库的交互实战
  9. hbase 客户端_读《HBase权威指南》 客户端API:基础知识
  10. 我的世界java版_我的世界Java版1.15版本
  11. Python全栈开发【基础-11】基本数据类型内置方法
  12. 华为交换机修改radius服务器地址,华为交换机radius认证-组网配置:pc+华为三层交换机+radius服务器(2003系统),求radius服务器的配置...
  13. javaweb项目tomcat检查不到当前模型的解决方法
  14. Wireshark分析流量包案例
  15. 手机app访问服务器数据库数据库文件夹,手机app怎么访问服务器数据库
  16. fast-lio2论文阅读 《FAST-LIO2: Fast Direct LiDAR-inertial Odometry》
  17. strcat函数的用法
  18. uni-app 微信支付
  19. Ubuntu系统中查看电脑驱动信息
  20. Dubbo 3 易用性升级之 Dubbo 官网大改版

热门文章

  1. Excel,公式生成的数据如何复制到另一个工作簿中?
  2. 【PaperReading】OpenHGNN:An Open-Source Toolkit for Heterogeneous Graph Neural Networks
  3. msvcp140.dll丢失的解决方法win10_简单方便一点的方法推荐
  4. 蓉乇辟坠吠悠吹爸现镁
  5. 20230308英语学习
  6. Java入门第88课——使用replaceAll实现字符串替换
  7. Handler用法及解析
  8. 2021年高压电工证考试及高压电工实操考试视频
  9. 云计算 python(零基础2)
  10. leetcode 数组:简单题 第四页