iOS 视图,动画渲染机制探究

腾讯Bugly特约作者:陈向文

终端的开发,首当其冲的就是视图、动画的渲染,切换等等。用户使用 App 时最直接的体验就是这个界面好不好看,动画炫不炫,滑动流不流畅。UI就是 App 的门面,它的体验伴随着用户使用 App 的整个过程。如果UI失败,用户是不会有打开第二次的欲望的。

iOS 为开发者提供了丰富的 Framework(UIKit,Core Animation,Core Graphic,OpenGL 等等)来满足开发从上层到底层各种各样的需求。不得不说苹果很牛逼,很多接口你根本不需要理解背后的原理就能上手使用并且满足你大部分的需求,但是,如果遇到性能问题就容易抓瞎。易用性跟优化就是个矛盾体,就像 ARC 一样,当你没有遇到内存问题的时候用得很爽,一旦遇到了,就要要求你比在用 MRC 的时候更加了解 iOS 的内存机制。UI 亦是如此。况且,作为鹅厂的员工当然不能仅限于知道怎么用。我们要知其然还要知其所以然。好了,废话不说,我们进入主题:看看 iOS 是如何渲染视图和动画的,以及在我们遇到渲染的性能问题时怎么做优化。

(注意:以下内容是笔者的一些踩坑经验和总结, 欢迎探讨!)

先来看看官方对 Core Animation 的一段说明:

可以看出iOS渲染视图的核心是 Core Animation。从底层到上层依此是 GPU->(OpenGL、Core Graphic) -> Core Animation -> UIKit。

在 iOS上,动画和视图的渲染其实是在另外一个进程做的(下面我们叫这个进程 render server),在 iOS 5 以前这个进程叫 SpringBoard,在 iOS 6 之后叫 BackBoard。

下面这幅图是使用项目录制视频的时候(大量视图渲染),整个系统的进程情况:

可以很清楚地看到 BackBoard 这个进程的情况。

iOS 上视图或者动画渲染的各个阶段:

在 APP 内部的有4个阶段:

布局:在这个阶段,程序设置 View / Layer 的层级信息,设置 layer 的属性,如 frame,background color 等等。

创建 backing image:在这个阶段程序会创建 layer 的 backing image,无论是通过 setContents 将一个 image 传給 layer,还是通过 [drawRect:] 或 [drawLayer: inContext:] 来画出来的。所以 [drawRect:] 等函数是在这个阶段被调用的。

准备:在这个阶段,Core Animation 框架准备要渲染的 layer 的各种属性数据,以及要做的动画的参数,准备传递給 render server。同时在这个阶段也会解压要渲染的 image。(除了用 imageNamed:方法从 bundle 加载的 image 会立刻解压之外,其他的比如直接从硬盘读入,或者从网络上下载的 image 不会立刻解压,只有在真正要渲染的时候才会解压)。

提交:在这个阶段,Core Animation 打包 layer 的信息以及需要做的动画的参数,通过 IPC(inter-Process Communication)传递給 render server。

在 APP 外部的2个阶段:

当这些数据到达 render server 后,会被反序列化成 render tree。然后 render server 会做下面的两件事:

  • 根据 layer 的各种属性(如果是动画的,会计算动画 layer 的属性的中间值),用 OpenGL 准备渲染。
  • 渲染这些可视的 layer 到屏幕。

如果做动画的话,最后的两个步骤会一直重复知道动画结束。

我们都知道 iOS 设备的屏幕刷新频率是 60HZ。如果上面的这些步骤在一个刷新周期之内无法做完(1/60s),就会造成掉帧。

我们看看有哪些操作可能会过度消耗 CPU 或者 GPU,从而造成掉帧。

  • 视图上有太多的 layer 或者几何形状:
    如果视图的层级结构太复杂的话,当某些视图被渲染或者 frame 被修改的话,CPU 会花比较多得时间去重新计算 frame。尤其如果用 autolayout 的话,会更消耗 CPU。同时过多的几何结构会大大增多需要渲染的OpenGL triangles 以及栅格化的操作(将 OpenGL 的 triangles 转化成像素)
  • 太多的 overdraw:overdraw 是指一个像素点被多次地用颜色填充。这个主要是由于一些半透明的 layer 相互重叠造成的。GPU 的 fill-rate(用颜色填充像素的速率)是有限的。如果 overdraw 太多的话,势必会降低 GPU 的性能。
  • 视图的延后载入:iOS 只有在展示 viewcontroller 的 view 或者访问 viewcontroller 的 view,比如说 someviewcontroller.view 的时候才会加载view。如果在用户点击了某个 button,并且在 button 的响应函数里做了很多消耗 cpu 的工作,这个时候如果 present 某个 viewcontroller 的话,会容易卡顿,尤其是如果viewcontroller 要从 database 里获取数据,或者从 nib 文件初始化 view 或者加载图片会更卡。
  • 离屏的绘制:离屏的绘制有两种情况:1. 有些效果(如 rounded corners,layer masks,drop shadows 和 layer rasterization)不能直接的绘制到屏幕上,必须先绘制到一个 offscreen 的 image context 上,这种操作会引入额外的内存和 CPU 消耗。2. 实现了 drawRect 或者 drawLayer:inContext:,为了支持任意的绘制,core graphic 会创建一个大小跟要画的 view 一样的 backing image。并且当画完的以后要传输到 render server 上渲染。所以没事不要重载 drawRect 等函数却什么都不做。
  • 图片解压:用 imageNamed:从 bundle 里加载会立马解压。一般的情况是在赋值给 UIImageView 的 image 或者 layer 的 contents 或者画到一个 core graphic context 里才会解压。

渲染性能优化的注意点:

隐藏的绘制:catextlayer 和 uilabel 都是将 text 画入 backing image 的。如果改了一个包含 text 的 view 的 frame 的话,text 会被重新绘制。

Rasterize:当使用 layer 的 shouldRasterize 的时候(记得设置适当的 laye r的 rasterizationScale),layer 会被强制绘制到一个 offscreen image 上,并且会被缓存起来。这种方法可以用来缓存绘制耗时(比如有比较绚的效果)但是不经常改的 layer,如果 layer 经常变,就不适合用。

离屏绘制: 使用 Rounded corner, layer masks, drop shadows 的效果可以使用 stretchable images。比如实现 rounded corner,可以将一个圆形的图片赋值于 layer 的 content 的属性。并且设置好 contentsCenter 和 contentScale 属性。

Blending and Overdraw :如果一个 layer 被另一个 layer 完全遮盖,GPU 会做优化不渲染被遮盖的 layer,但是计算一个 layer 是否被另一个 layer 完全遮盖是很耗 cpu 的。将几个半透明的 layer 的 color 融合在一起也是很消耗的。

我们要做的:

  • 设置 view 的 backgroundColor 为一个固定的,不透明的 color。
  • 如果一个 view 是不透明的,设置 opaque 属性为 YES。(直接告诉程序这个是不透明的,而不是让程序去计算)

这样会减少 blending 和 overdraw。

如果使用 image 的话,尽量避免设置 image 的 alpha 为透明的,如果一些效果需要几个图片融合而成,就让设计用一张图画好,不要让程序在运行的时候去动态的融合。

好了,介绍完这些渲染优化需要注意的点,让我们用 instrument 的 Core Animation 和 GPU Driver 来看看一些具体的例子。

Core Animation:

Color Blended Layers:看半透明 layer 的遮盖情况。从绿到红,越红遮盖越大。

这下这两幅图是测量项目详情页的半透明的 layer 的情况。可以看到详情页这里半透明的 layer 还是比较多的,但不是说半透明的 layer 很多,范围很大就要优化,要参看 GPU Driver 的测量情况看,下面会介绍

Color Hits Green and Misses Red:当使用shouldRasterize的时候,layer drawing 会被缓存起来,如果 rasterized 的 layer 需要被重新绘制,会标示红。

下面是项目消息列表的 cell 设置 shouldRasterize=YES 的情况,每个 cell 的 layer 的 backing image 会被缓存起来,如果往下滚动 tableview 的话,由于 cell 会被复用,这样 layer 就会被重绘,会被标红。

Color Offscreen-Rendered Yellow:如果要做 offscreen drawing 的话,会标成黄色。

下面是项目的首页,由于圆形头像的实现方式是设置 roundedCorner 的属性来实现,所以会触发 offscreen drawing。

Core Animation Template 只是能让开发者直观地看到哪些地方有可能需要优化,但是到底要不要优化,还是要看 GPU Driver 的表现。

GPU Driver

Renderer Utilization ——如果这个值大于50%的话,表示 GPU 的性能受到 fill-rate 的限制,可能有太多的 Offscreen rendering,overdraw,blending。

Tiler Utilization ——如果这个值大于50%,表示可能有太多的 layers。

我们以上面的那个项目的详情页为例,看看 GPU driver 的测量:

可以看到这 Renderer Utilization 是20%左右,Tiler Utilization 时15%,可以不用优化,(当然这里我们先不考虑 CPU 的使用情况,只是单单针对上面 Core Animation Template 的一些测量说不用做针对性的优化)

这里想说的一点是,以上所说的点只是在我们遇到渲染性能问题的时候給我们提供优化的方向跟思路。优化往往代表着更复杂,难懂的代码,在没有遇到渲染性能问题的时候不要过度优化。

希望对大家有帮助!

本文为腾讯Bugly原创文章,如需转载,请标明出处。

想了解更多干货,请搜索关注公众号:腾讯Bulgy,或搜索微信号:weixinBugly,关注我们


腾讯Bugly

Bugly是腾讯内部产品质量监控平台的外发版本,支持iOS和Android两大主流平台,其主要功能是App发布以后,对用户侧发生的crash以及卡顿现象进行监控并上报,让开发同学可以第一时间了解到app的质量情况,及时修改。目前腾讯内部所有的产品,均在使用其进行线上产品的崩溃监控。

腾讯内部团队4年打磨,目前腾讯内部所有的产品都在使用,基本覆盖了中国市场的移动设备以及网络环境,可靠性有保证。使用Bugly,你就使用了和手机QQ、QQ空间、手机管家相同的质量保障手段

时间: 2024-10-08 16:18:56

iOS 视图,动画渲染机制探究的相关文章

IOS视图动画

第一种:     //开始准备动画     [UIView beginAnimations:nil context:nil];     //设置动画的时间     [UIView setAnimationDuration:2.0f];     //设置动画次数     [UIView setAnimationRepeatCount:5];     //设置回放(回到初始位置,默认为NO)     [UIView setAnimationRepeatAutoreverses:YES];     /

仿写及比较标哥的iOS时钟动画

一.前言 以前看各种绚丽的UI特效动画代码,采用的方法是会先运行一篇,然后直接去看实现代码.初学时抱着瞻仰的态度去接触,去认识,是没有错的.但是在了解了像素.动画渲染机制,CoreAnimation API,推导过二维.三维的仿射矩阵之后,我们可以改变阅读UI动画博文或者是源码的方式了. Talk is cheap, show me the code——Linus Torvalds. 大量的仿写:一定一定要多写——叶孤城__ 在CodeReview线下大会上的发言. 最近安居客.猿题库.蘑菇街.

iOS离屏渲染的解释

重开一个环境(内存.资源.上下文)来完成(部分)图片的绘制 指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作 意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作. 红色代表GPU需要做额外的工作来渲染View,绿色代表GPU无需做额外的工作来处理bitmap. 界面渲染过程 RunLoop有一个60fps的回调,即每16.7ms绘制一次屏幕,所以view的绘制必须在这个时间内完成,view内容的绘制是CPU的工作,然后把绘制的内容交给GPU渲染,包括多个Vie

IOS视图缩放显示动画效果

效果:视图从大--小缩放显示/小--大 (只是比例问题) 方法1.直接show出view的时候:把下面的这段代码加到viewController或者view出现的时候就OK self.view.transform = CGAffineTransformMakeScale(1.0f, 1.0f);//将要显示的view按照正常比例显示出来  [UIView beginAnimations:nil context:UIGraphicsGetCurrentContext()];  [UIView se

转---视图动画和坐标系介绍!

Core Animation基础 Core Animation利用了硬件加速和架构上的优化来实现快速渲染和实时动画.当视图的drawRect:方法首次被调用时,层会将描画的结果捕捉到一个位图中,并在随后的重画中尽可能使用这个缓存的位图,以避免调用开销很大的drawRect:方法.这个过程使Core Animation得以优化合成操作,取得期望的性能. Core Animation把和视图对象相关联的层存储在一个被称为层树的层次结构中.和视图一 样,层树中的每个层都只有一个父亲,但可以嵌入任意数量

iOS离屏渲染简书

更详细地址https://zsisme.gitbooks.io/ios-/content/chapter15/offscreen-rendering.html(包含了核心动画) GPU渲染机制: CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示. GPU屏幕渲染有以下两种方式: On-Screen Rendering意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显

iOS核心动画高级技巧 - 8

iOS核心动画高级技巧 - 1 iOS核心动画高级技巧 - 2 iOS核心动画高级技巧 - 3 iOS核心动画高级技巧 - 4 iOS核心动画高级技巧 - 5 iOS核心动画高级技巧 - 6 iOS核心动画高级技巧 - 7 15. 图层性能 图层性能 要更快性能,也要做对正确的事情. ——Stephen R. Covey 在第14章『图像IO』讨论如何高效地载入和显示图像,通过视图来避免可能引起动画帧率下降的性能问题.在最后一章,我们将着重图层树本身,以发掘最好的性能. 15.1 隐式绘制 隐式

iOS 离屏渲染的研究

GPU渲染机制: CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示. GPU屏幕渲染有以下两种方式: On-Screen Rendering意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行. Off-Screen Rendering意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作. 特殊的离屏渲染:如果将不在GPU的当

[iOS]过渡动画之高级模仿 airbnb

注意:我为过渡动画写了两篇文章:第一篇:[iOS]过渡动画之简单模仿系统,主要分析系统简单的动画实现原理,以及讲解坐标系.绝对坐标系.相对坐标系,坐标系转换等知识,为第二篇储备理论基础.最后实现 Mac 上的文件预览动画.第二篇:[iOS]过渡动画之高级模仿 airbnb,主要基于第一篇的理论来实现复杂的界面过渡,包括进入和退出动画的串联.最后将这个动画的实现部分与当前界面解耦,并封装为一个普适(其他类似界面也适用)的工具类. 这两篇文章将会带你学到如何实现下图 airbnb 首页类似的过渡动画