概念
FPS:表示每秒渲染的帧数,通过用于衡量画面的流畅度,数值越高则表示画面越流畅。CPU:负责对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制(Core Graphics)。GPU: 负责纹理的渲染(将数据渲染到屏幕)。垂直同步技术: 让CPU和GPU在收到vSync信号后再开始准备数据,防止撕裂感和跳帧,通俗来讲就是保证每秒输出的帧数不高于屏幕显示的帧数。双缓冲技术:iOS是双缓冲机制,前帧缓存和后帧缓存,cpu计算完GPU渲染后放入缓冲区中,当gpu下一帧已经渲染完放入缓冲区,且视频控制器已经读完前帧,GPU会等待vSync(垂直同步信号)信号发出后,瞬间切换前后帧缓存,并让cpu开始准备下一帧数据。
滑动卡顿的原因
- 在 VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。随后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
卡顿监测
-
监测fps,具体见YYFpsLabel,利用
CADisplayLink统计delta(>=1)秒内的执行次数count,然后用count/delta得到fps,局部代码如下:1
2
3
4
5
6
7
8
9
10//代码在 YYKitDemo -> other -> YYFpsLabel
//记录CADisplayLink的方法调用
- (void)tick:(CADisplayLink *)link {
_count++; //统计次数
NSTimeInterval delta = link.timestamp - _lastTime; //距离上次统计的时间差
if (delta < 1) return; //时间差不到一秒不能计算
float fps = _count / delta; //计算出fps
_lastTime = link.timestamp; //最后时间重置
_count = 0; //次数归零
} -
基于runloop,具体使用微信团队的matrix
原理:Matrix卡顿监控在 Runloop 的起始最开始和结束最末尾位置添加 Observer,从而获得主线程的
开始和结束状态。卡顿监控起一个子线程定时检查主线程的状态,当主线程的状态运行超过一定阈值则认为主线程卡顿,从而标记为一个卡顿。同时,我们也认为 CPU 过高也可能导致应用出现卡顿,所以在子线程检查主线程状态的同时,如果检测到 CPU 占用过高,会捕获当前的线程快照保存到文件中。目前微信应用中认为,单核 CPU 的占用超过了 80%,此时的 CPU 占用就过高了。 -
基于runloop,使用LXDAppFluecyMonitor项目
-
Instruments Time Profiler -> Call Tree Options :
- Separete By Thread :按线程划分
- Invert Call Tree :逆向调用树,方便查看调用顺序
- Hide System Libraries:隐藏系统库
滑动卡顿优化方案
-
CPU 资源消耗原因和解决方案
- 尽量不要动态创建销毁对象,最好复用对象,因为对象创建分配内存比较消耗CPU资源。
- 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属
- 如果需要手写代码布局,不用Autolayout
- 尽量把耗时的操作放到子线程,文本处理(尺寸计算、绘制),图片处理(异步解码、异步绘制)
1
2
3
4
5
6
7
8
9
10
11- (void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
} - 预排版,尽量手动布局,在子线程,将
model用一个对象layout包装,根据model计算出布局信息(如组件frame、cell高度、富文本信息等)存入对象layout,然后主线程刷新传给cell。
-
GPU 资源消耗原因和解决方案
- 视图的混合,尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
- GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
- 尽量减少视图数量和层次
- 减少透明的视图(alpha<1),不透明的就设置opaque为YES
- 尽量避免出现离屏渲染,具体参考
AsyncDisplayKit
- AsyncDisplayKit源码
- AsyncDisplayKit 是 Facebook 开源的一个用于保持 iOS 界面流畅的库,
- SDK 认为,阻塞主线程的任务,主要分为上面这三大类。文本和布局的计算、渲染、解码、绘制都可以通过各种方式异步执行,但 UIKit 和 Core Animation 相关操作必需在主线程进行。ASDK 的目标,就是尽量把这些任务从主线程挪走,而挪不走的,就尽量优化性能。
- ASDK 把大量常用控件都封装成了 ASNode 的子类,比如 Button、Control、Cell、Image、ImageView、Text、TableView、CollectionView 等
参考文档
Matrix-iOS 卡顿监控
matrix项目
YYKit项目
LXDAppFluecyMonitor项目
iOS卡顿监测方案总结
iOS 保持界面流畅的技巧