APP的界面优化
什么样的界面让你觉得需要被优化呢?
就是界面会卡顿咯。
下面介绍
- 卡顿原理
- 卡顿检测
- 实战
1、界面为什么会卡顿呢
-
先要来说说计算机显示的过程,界面的显示是通过
CPU
、GPU
、显示器
协同工作,最终将图片显示到屏幕
-
图像 -> CPU将图片解码,交给GPU -> GPU进行图像的渲染 -> 存储到到帧缓存区 -> 视频控制器进行读取帧缓存区信息,并刷新部件(视频控制器只是负责帧缓存区与显示器的对应关系) -> 显示器逐行扫描显示。
在最初的时候,FrameBuffer只有一个,但只有一个缓冲区会屏幕会很大概率出现问题,为了解决效率问题就引入了双缓冲区
。
-
双缓冲区
是在帧缓存区中开辟两个缓冲区
,一个缓冲区通过视频控制器进行当前帧数据的读取显示,另一个缓冲区进行接收下一帧GPU渲染的图像。
但这样又会出现一个问题,画面撕裂
,就是显卡的输出帧速度大于显示器的速度,在显示器处理显卡一帧的过程中,显卡又把处理好的第二帧丢了过来,造成了画面撕裂
。为了解决这个问题,采用了垂直同步信号
。
-
垂直同步
又称场同步(Vertical synchronization
),从CRT显示器的显示原理来看,单个像素组成了水平扫描线,水平扫描线在垂直方向的堆积形成了完整的画面。显示器的刷新率受显卡DAC控制,显卡DAC完成一帧的扫描后就会产生一个垂直同步信号。
简而言之:垂直同步就是加锁,在当前读取的帧数据结束之前,不会读取下一帧的数据。
在这之后出现的问题就是卡顿了
- 第一帧显示完成后垂直信号发出信号让视频控制器指向另一个帧缓冲区时,第二帧还在处理中,因此显示器还在显示第一帧(掉帧),下一帧处理完成后直接显示下一帧。这就是出现
卡顿
的原因。
为了避免出现卡顿,我们需要对卡顿进行监测
2、卡顿监测
一般分为两类:
2.1、FPS
-
FPS
:通过CADisplayLink
来计算刷新时间频率。应用的刷新频率应保持60fps左右,那么1次
刷新间隔就是1000ms/60 = 16.67ms
,所以在16.67ms
内没有准备好下一帧,就会造成卡顿 -
FPS主要是通过
CADisplayLink
实现的,用时间差来计算每次的刷新时间从而得到刷新频率。
2.1、主线程卡顿监测
-
主线程卡顿监测
:通过子线程去监测主线程RunLoop,从任务开始(kCFRunLoopBeforeSources)
到任务结束(kCFRunLoopAfterWaiting)
间的耗时,过长时基本上就认为可能有卡顿了。
卡顿检测第三方库
swift:ANREye
3、界面优化
--CPU层面--
3. 1、预排版
预排版
:提前计算布局
,如cell的高度,(提前计算,后面直接使用)
我们可以单独在一个预排版的子线程
去做一些事情:
- frame的计算
- 控件层级的部署
- 渲染所需数据的处理
- Model模型的数据解析等
尽量提前计算好布局,在需要时一次性调整好对应属性,而不要多次、频繁的计算和调整这些属性。
3. 2、Autolayout
Autolayout
在大部分情况下也能很好的提升开发效率,但是Autolayout对于复杂视图
来说常常会产生严重的性能问题。随着视图数量的增长,Autolayout 带来的 CPU 消耗会呈指数级
上升。
所以复杂的页面最好使用纯代码来布局。
3. 3、预解码 & 预渲染
3. 4、对象创建、调整、销毁
-
对象创建:尽量做到
懒加载
。不用的对象不进行创建;- 若视图
不需要响应事件
,用CALayer
显示。CALayer 比 UIView更轻量级
。
- 若视图
-
对象调整:减少对
UIView
和CALayer
的属性修改
-
对
CALayer
的属性修改
,实际上是运行时resolveInstanceMethod
为对象临时添加一个方法,把对应的属性存储到内部的Dictionary中,并通知delegate、完成调整等等,非常耗时。 -
对
UIView
的属性修改(如frame、bounds、transform等)
,实际是从CALayer映射的,所以修改UIView的资源消耗更加大。
-
-
对象销毁:有
大量对象释放
时,是非常耗时的,尽量挪到子线程去释放
3. 4、文本处理
-
1、对文本没有特殊需求的话,可以使用UILabel的内部方法来进行计算,需要放到子线程中,防止主线程被阻塞
-
计算文本宽高:
[NSAttributedString boundingRectWithSize:options:context:]
-
文本绘制:
[NSAttributedString drawWithRect:options:context:]
-
-
2、自定义文本控件,可以使用
TextKit
或最底层的CoreText
对文本异步绘制。- 使用
CoreText
对象创建好后,能直接获取文本的宽高等信息,避免了多次计算
(调整和绘制都需要计算一次)。CoreText直接使用了CoreGraphics占用内存小,效率高
- 使用
3. 4、图片处理
-
1、
UIImage
或者CGImageSource
的方法创建图片时,图片不会马上开始解码,而是当图片设置到UIImageView
或者CALayer.contents
上,且CALayer 被提交到 GPU 前
,CGImage
中的数据才会得到解码
。这一步是发生在主线程的,并且不可避免。如果想要绕开这个机制,常见的做法是在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。如SDWebImage
三方框架中对图片编解码的处理。这就是Image的预解码 -
2、可以将图像的绘制在子线程中进行
3. 5、小处理
-
避免使用透明view
-
避免使用
addView
给cell动态添加view
-
按需加载
,例如在TableView中滑动时不加载图片,使用默认占位图,而是在滑动停止时加载 -
尽量使用PNG图片,不使用JPGE图片
--GPU层面--
GPU能干的事情比较单一;接收提交的纹理(Texture)和顶点描述(三角形),应用变换(transform)、混合并渲染,然后输出到屏幕上。通常你所能看到的内容,主要也就是纹理(图片)和形状(三角模拟的矢量图形)两类。
-
1、尽量将多张图合为一张进行显示
-
2、尽量减少视图数量和层次
-
3、离屏渲染:
CALayer的border、圆角、阴影、遮罩(mask)
,最彻底的解决办法,就是把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。 -
4、异步渲染:参考Graver
网友评论