iOS探索:UI视图之卡顿、掉帧及绘制原理

在开始理解卡顿、掉帧及绘制原理前,首先让我们先了解下图像的显示原理

图像显示原理

WX20181206-150708@2x.png
  • 关于CPU和GPU都是通过总线连接起来的,在CPU当中输出的往往是一个位图,再经由总线在合适的时机传递个GPU

  • GPU拿到这个位图之后,会对这个位图的图层进行渲染,包括纹理的合成等

  • 之后会把这个结果放到帧缓冲区中,然后视频控制器会按照VSync信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器,达到最终的显示效果

那么接下来让我们看一下CPU和GPU分别做了哪些事情

WX20181206-153514@2x.png
  • 首先当我们创建一个UIView控件的时候,其中负责显示的CALayer

  • CALayer中有一个contents属性,就是我们最终要绘制到屏幕上的一个位图,比如说我们创建了一个UILabel,那么在contents里面就放了一个关于Hello world的文字位图

  • 然后系统会在一个合适的时机回调给我们一个drawRect:的方法,这个方法中我们可以去绘制一些自定义的内容

  • 绘制好了之后,最终会由Core Animation这个框架提交给GPU部分的OpenGL渲染管线,进行最终的位图的渲染,包括纹理合成等,然后显示在屏幕上

那么CPU和GPU具体做了哪些工作承担呢

CPU

具体分为四个阶段

  • Layout:这里主要涉及到一些UI布局,文本计算等,例如一个label的size

  • Display:绘制阶段,例如drawRect方法就在这一步骤中

  • Prepare:图片的编解码等操作在此步骤中

  • Commit:提交位图

GPU渲染管线

  • 顶点着色

  • 图元装配

  • 光栅化

  • 片段着色

  • 片段处理

UI卡顿、掉帧的原因

WX20181206-160621@2x.png

在显示器中是固定的频率,比如iOS中是每秒60帧(60FPS),即每帧16.7ms

从上图中可以看出,每两个VSync信号之间有时间间隔(16.7ms),在这个时间内,CPU主线程计算布局,解码图片,创建视图,绘制文本,计算完成后将内容交给GPU,GPU变换,合成,渲染(详细可学习 OpenGL相关课程),放入帧缓冲区

假如16.7ms内,CPU和GPU没有来得及生产出一帧缓冲,那么这一帧会被丢弃,显示器就会保持不变,继续显示上一帧内容,这就将导致导致画面卡顿

所以无论CPU,GPU,哪个消耗时间过长,都会导致在16.7ms内无法生成一帧缓存

卡顿、掉帧优化方案切入点

  • CPU
    CPU在准备下一帧的所做的工作非常多导致耗时,基于减轻CPU工作时长和压力来达到一个优化效果
    1、部分对象的创建、调整和销毁可以放到子线程去做
    2、预排版( 布局计算、文本计算),这些计算也可以放到子线程去做,这样主线程也可以有更多的时间去响应用户的交互
    3、预渲染(文本等异步绘制、图片编解码等)

  • GPU
    1、纹理渲染:假如说我们触发了离屏渲染,例如我们设置圆角时对maskToBounds的设置,包括一些阴影、蒙层等都会触发GPU层面的离屏渲染,对于这种情况下,GPU对于纹理渲染的工作量就会非常的大,我们可以基于此对GPU进行优化,就是尽量减少离屏渲染,我们也可以通过CPU的异步绘制来减轻GPU的压力

    2、视图混合: 比如说我们视图层级比较复杂,视图之间层层叠加,那么GPU就要做每一个视图的合成,合成每一个像素点的像素值,如果我们可以减少视图的层级,也是可以减轻GPU的压力,我们也可以通过CPU的异步绘制机制来达到一个提交的位图本身就是一个层级比较少的位图

UIView的绘制原理

流程图
QQ20181206-211905@2x.png
  • 当我们调用[UIView setNeedsDisplay]这个方法时,其实并没有立即进行绘制工作,系统会立刻调用CALayer的同名方法,并且会在当前layer上打上一个标记,然后会在当前runloop将要结束的时候调用[CALayer display]这个方法,然后进入我们视图的真正绘制过程

  • 而在[CALayer display]这个方法的内部实现中会判断这个layer的delegate是否响应displayLayer:这个方法,如果不响应这个方法,就会进入到系统绘制流程中;如果响应这个方法,那么就会为我们提供异步绘制的入口

上面就是UIView的绘制原理,接下来我们看一下系统绘制流程是怎样的

老规矩,先上流程图
QQ20181206-213639@2x.png
  • 在CALayer内部会先创建backing store,我可以理解为CGContext,我们一般在drawRect:方法中通过上下文堆栈当中取出栈顶的context,也就是上下文

  • 然后这个layer会判断是否有代理,如果没有代理,那么就会调用[CALayer drawInCotext:];如果有代理,会调用代理的drawLayer:inContext:方法,然后做当前视图的绘制工作这一步是发生在系统内部的),然后在一个合适的时机给与我们这个十分熟悉的[UIView drawRect:]方法的回调,[UIView drawRect:]这个方法默认是什么都不做,,系统给我们开这个口子是为了让我们可以再做一些其他的绘制工作

  • 然后无论是哪个分支,最终都会由CALayer上传对应的backing store(可以理解为位图)给GPU,然后就结束了系统默认的绘制流程

那么问题来了,我们如何进行异步绘制呢

实际上我们就需要借用系统给开的这个口子,即[layer.delegate displayLayer:]

  • 在这个异步绘制过程中就需要代理负责生成对应的bitmap(位图)

  • 同时设置bitmap作为layer.contents属性的值

国际惯例,流程图走一波(原谅我画图能力实在有限TT)

QQ20181206-220620@2x.png
  • 假如说我们在某一个时机调用了[view setNeedsDisplay]这个方法,系统会在当前runloop将要结束的时候调用[CALyer display]方法,然后如果我们这个layer的代理实现了[view displayLayer]这个方法

  • 然后会通过子线程的切换,我们在子线程中去做一个位图的绘制,主线程可以去做一些其他的操作

  • 在子线程中第一步先通过CGBitmapContextCreate()方法来创建一个位图的上下文,然后我们通过CoreGraphic API可以做当前UI控件的一些绘制工作,最后我们再通过CGBitmapContextCreateImage()这个函数来根据当前所绘制的上下文来生成一张CGImage图片

  • 最后回到主线程来提交这个位图,设置layer的contents属性,这样就完成了一个UI控件的异步绘制过程

离屏渲染 (便于理解视图卡顿、掉帧中对GPU的开销)

离屏渲染指的是GPU在当前屏幕缓冲区以外开辟了一个缓冲区进行渲染操作

当前屏幕渲染不需要额外创建新的缓存,也不需要开启新的上下文,相对于离屏渲染性能更好。但是受当前屏幕渲染的局限因素限制(只有自身上下文、屏幕缓存有限等),当前屏幕渲染有些情况下的渲染解决不了的,就使用到离屏渲染

离屏渲染对性能的的代价是很高的,主要体现在:

  • 创建了新的缓冲区

  • 上下文的频繁切换

导致产生离屏渲染的原因:

  • shouldRasterize(光栅化)

  • masks(遮罩)

  • shadows(阴影)

  • edge antialiasing(抗锯齿)

  • group opacity(不透明)

  • 复杂形状设置圆角等

  • 渐变

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

推荐阅读更多精彩内容

  • 1 CALayer IOS SDK详解之CALayer(一) http://doc.okbase.net/Hell...
    Kevin_Junbaozi阅读 5,118评论 3 23
  • 作为一个iOS小菜鸟,当我们需求做完之后,我们该干什么?当然是学习!最近看了很多关于iOS性能优化的文章,为了便于...
    小虾啦阅读 2,927评论 1 4
  • 绘制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一个像素是如何绘制到屏幕上去的?有很多...
    阿狸旅途T恤阅读 1,616评论 0 7
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,082评论 1 32
  • 卷首语 欢迎来到 objc.io 的第三期! 这一期都是关于视图层的。当然视图层有很多方面,我们需要把它们缩小到几...
    评评分分阅读 1,753评论 0 18