今天我们聊下界面卡顿的原理以及优化
界面卡顿
图像显示过程
通常来说,计算机中的显示过程是如下图所示:通过上图可知,显示过程是通过CPU、GPU、显示器
协调工作来将图片显示到屏幕上。具体分以下几步:
- 1.
CPU计算
好显示内容,提交至GPU - 2.
GPU经过渲染
完成后将渲染的结果放入FrameBuffer
(帧缓存区) - 3.随后
视频控制器
会按照VSync信号
逐行读取FrameBuffer的数据
- 4.经过
数模转换
传递给显示器进行显示
iOS双缓冲机制+VSync
- 刚开始,
FrameBuffer只有一个
,这种情况下FrameBuffer
的读取和刷新
有很大的效率
问题,为了解决这个问题,引入了双缓存区
。即双缓冲机制
。在这种情况下,GPU会预先渲染好一帧放入FrameBuffer
,让视频控制器读取,当下一帧渲染
好后,GPU
会直接将视频控制器的指针指向第二个FrameBuffer
。 -
双缓存机制
虽然解决了效率问题
,但是随之而言的是新的问题,当视频控制器还未读取完成时
,例如屏幕内容刚显示一半,GPU将新的一帧内容提交到FrameBuffer,并将两个FrameBuffer而进行交换后,视频控制器就会将新的一帧数据的下半段显示到屏幕上,造成屏幕撕裂
现象,(安卓手机会出现这样的问题,后续的安卓系统推出三缓存机制) - 为了解决这个问题,采用了
垂直同步信号机制
。当开启垂直同步
后,GPU
会等待
显示器的VSync信号
发出后,才进行新的一帧渲染
和FrameBuffer更新
。而目前iOS设备中采用的正是双缓存区+VSync
卡顿原因
上面说了展示过程,下面就来说下卡顿原因。
- 在
VSync
信号到来后,系统图形服务会通过CADisplayLink
等机制通知App,App主线程
开始在CPU中计算显示内容
。随后CPU
会将计算好的内容
提交到GPU
去,由GPU
进行变换、合成、渲染
。 - 随后
GPU会把渲染结果
提交到帧缓冲区
去,等待下一次VSync信号
到来时显示到屏幕
上。 - 由于
垂直同步的机制
,如果在一个VSync时间内
,CPU或者GPU没有完成内容提交
,则那一帧
就会被丢弃
,等待下一次机会再显示
,而这时显示屏
会保留之前的内容不变
。所以可以简单理解掉帧
为过时不候
上图是一个显示过程,第1帧在VSync到来前,处理完成,正常显示,第2帧在VSync到来后,仍在处理中,此时屏幕不刷新,依旧显示第1帧,此时就出现了掉帧
情况,渲染时就会出现明显的卡顿
现象,CUP和GPU
不论是哪个阻碍
了显示流程
,都会造成掉帧
现象,所以为了给用户提供更好的体验,在开发中,我们需要进行卡顿检测
以及相应的优化
卡顿监控
卡顿监控一般有两种:
- 1.
FPS监控
:为了保持流程的UI交互,App的刷新频次应该保持在60fps
左右,其原因是因为iOS设备默认的刷新频率是60次/秒
,而1次
刷新(即VSync信号发出)的间隔是1000ms/60 = 16.67ms
,所以如果在16.67ms内没有准备好下一帧数据
,就会产生卡顿
- 2.
主线程卡顿监控
:通过子线程监测主线程的RunLoop
,判断两个状态(kCFRunLoopBeforeSources
和kCFRunLoopAfterWaiting
)之间的耗时
是否达到一定阈值
FPS监控
FPS的监控,参照YYKit
中的YYFPSLabel
,主要是通过CADisplayLink实现
。借助link的时间差
,来计算一次刷新刷新所需的时间
,然后通过刷新次数/时间差
得到刷新频次,并判断是否其范围,通过显示不同的文字颜色来表示卡顿严重程度。核心代码实现如下:
如果只是简单的监测,使用FPS足够了。
主线程卡顿监控
除了FPS
,还可以通过RunLoop来监控
,因为卡顿
的是事务,而事务
是交由主线程
的RunLoop处理
的。
【实现思路】:检测主线程
每次执行消息循环的时间
,当这个时间大于规定的阈值时
,就记为发生了一次卡顿
。这个也是微信卡顿三方matrix的原理
以下是一个简易版RunLoop监控的实现
卡顿检测还有一些优秀的第三方供我们选择
- Swift的卡顿检测第三方ANREye,其主要思路是:
创建子线程
进行循环监测
,每次检测
时设置标记置为true
,然后派发任务到主线程
,标记置为false
,接着子线程睡眠超过阈值
时,判断标记是否为false
,如果没有
,说明主线程发生了卡顿
- OC可以使用微信的matrix 思路:是通过
监听主线程执行消息时间是否大于阀值
。滴滴的DoraemonKit 思路:不断查询主线程的RunLoop状态
界面优化
CPU层面的优化
- 1.尽量用
轻量级的对象代替重量级的对象
,可以对性能有所优化,例如不需要相应触摸事件的控件
,用CALayer
代替UIView
- 2.尽量
减少
对UIView
和CALayer
的属性修改
-
CALayer
内部并没有属性
,当调用属性方法
时,其内部是通过运行时resolveInstanceMethod
为对象临时添加
一个方法,并将对应属性值保存
在内部的一个Dictionary中
,同时还会通知delegate
、创建动画
等,非常耗时
-
UIView相关的显示属性
,例如frame、bounds、transform
等,实际上都是从CALayer映射来
的,对其进行调整时,消耗的资源比一般属性要大
-
- 3.当有
大量对象释放
时,也是非常耗时的,尽量挪到后台线程
去释放
- 4.尽量
提前计算视图布局
,即预排版
,例如cell的行高
- 5.
Autolayout
在简单页面
情况下们可以很好的提升开发效率
,但是对于复杂视图
而言,会产生严重的性能问题
,随着视图数量的增长
,Autolayout
带来的CPU消耗
是呈指数上升
的。所以尽量使用代码布局
。如果不想手动调整frame等,也可以借助三方库,例如Masonry(OC)
、SnapKit(Swift)
、ComponentKit
、AsyncDisplayKit
等 - 6.文本处理的优化:当一个界面有大量文本时,其行高的计算、绘制也是非常耗时的
- 【1】如果对
文本没有特殊要求
,可以使用UILabel内部的实现方式,且需要放到子线程
中进行,避免阻塞主线程- 计算文本宽高:
[NSAttributedString boundingRectWithSize:options:context:]
- 文本绘制:
[NSAttributedString drawWithRect:options:context:]
- 计算文本宽高:
- 【2】自定义文本控件,利用
TextKit
或最底层的CoreText
对文本异步绘制
。并且CoreText
对象创建好后,能直接获取文本的宽高等信息,避免了多次计算(调整和绘制都需要计算一次)。CoreText
直接使用
了CoreGraphics占用内存小
,效率高
- 【1】如果对
- 7.图片处理(解码 + 绘制)
- 【1】当使用
UIImage
或CGImageSource
的方法创建图片时,图片的数据不会立即解码,而是在设置时解码(即图片设置到UIImageView/CALayer.contents
中,然后在CALayer
提交至GPU渲染
前,CGImage
中的数据才进行解码)。这一步是无可避免
的,且是发生在主线程
中的。想要绕开这个机制,常见的做法是在子线程中先将图片绘制 - 【2】当使用
CG开头的方法绘制图像到画布
中,然后从画布
中创建图片时,可以将图像的绘制在子线程中进行
- 【1】当使用
- 8.图片优化
- 【1】尽量使用
PNG
图片,不使用JPGE
图片 - 【2】通过
子线程预解码,主线程渲染
,即通过Bitmap
创建图片,在子线程赋值image
- 【3】优化图片大小,尽量
避免动态缩放
- 【4】尽量将
多张图合为一张
进行显示
- 【1】尽量使用
- 9.尽量
避免使用透明view
,因为使用透明view
,会导致在GPU中计算像素时
,会将透明view下层图层的像素也计算进来
,即颜色混合处理
。 - 10.
按需加载
,例如在TableView中滑动时不加载图片
,使用默认占位图,而是在滑动停止时加载
- 11.少使用
addView
给cell动态添加view
GPU层面优化
相对于CPU
而言,GPU主要是接收CPU提交的纹理+顶点
,经过一系列运算,最终混合并渲染
,输出到屏幕
上。
- 1.尽量
减少在短时间内大量图片的显示
,尽可能将多张图片合为一张显示
,主要是因为当有大量图片进行显示
时,无论是CPU的计算还是GPU的渲染
,都是非常耗时
的,很可能出现掉帧
的情况 - 2.尽量
避免
图片的尺寸超过4096×4096
,因为当图片超过这个尺寸
时,会先由CPU进行预处理
,然后再提交给GPU处理
,导致额外CPU资源消耗
- 3.
尽量减少视图数量和层次
,主要是因为视图过多且重叠
时,GPU会将其混合
,混合的过程也是非常耗时
的 - 4.尽量避免离屏渲染
- 5.
异步渲染
,例如可以将cell中的所有控件
、视图合成一张图片进行显示
。可以参考Graver三方框架
最后推荐个我的iOS交流群:[89 1 488 18 1 ]
'有一个共同的圈子很重要,结识人脉!里面都是iOS开发,全栈发展,欢迎入驻,共同进步!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)
- 以下资料在群文件可自行下载**
驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!**
作者:空白记忆
链接:https://juejin.cn/post/6909473334069559303
网友评论