卡顿的原理
图形处理的框架结构
图形显示的原理
卡顿的检测
屏幕刷新率
FPS是图像领域中的定义,是指画面每秒传输帧数,通俗来讲就是指动画或视频的画面数。
1、CADisplayLink监测
利用CADisplayLink
监测垂直同步信号
//定义 CADisplayLink
_link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
//vsync信号事件回调
- (void)tick:(CADisplayLink *)link {
if (_lastTime == 0) {
_lastTime = link.timestamp;
return;
}
_count++;
NSTimeInterval delta = link.timestamp - _lastTime;
if (delta < 1) return;
_lastTime = link.timestamp;
float fps = _count / delta;
_count = 0;
}
2、Runloop监测
原理如下:
给主线程的 runloop 添加 observer 事件,监听 runloop 的所有活动,利用信号量的机制,根据限定时间内,runloop的活动状态是否发生改变,来判断是否发生了卡顿。
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
LGBlockMonitor *monitor = (__bridge LGBlockMonitor *)info;
monitor->activity = activity;
// 发送信号
dispatch_semaphore_t semaphore = monitor->_semaphore;
dispatch_semaphore_signal(semaphore);
}
- (void)registerObserver{
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
//NSIntegerMax : 优先级最小
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
NSIntegerMax,
&CallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}
- (void)startMonitor{
// 创建信号
_semaphore = dispatch_semaphore_create(0);
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (YES)
{
// 超时时间是 1 秒,没有等到信号量,st 就不等于 0, RunLoop 所有的任务
long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
if (st != 0)
{
if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting)
{
if (++self->_timeoutCount < 2){
NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount);
continue;
}
// 一秒左右的衡量尺度 很大可能性连续来 避免大规模打印!
NSLog(@"检测到超过两次连续卡顿");
}
}
self->_timeoutCount = 0;
}
});
}
3、微信卡顿监测工具 Matrix
github上的地址:github.com/Tencent/mat…
该工具的原理也是利用对 runloop 的监听。但它不仅可以监测到卡顿,而且可以定位到卡顿的原因,报告堆栈信息等。
4、滴滴卡顿监测工具 DoraemonKit
github上的地址:github.com/didi/Doraem…
原理:开启一个子线程不断地Ping主线程,通过信号量控制来Ping主线程,判断主线程是否卡顿
滑动优化方案
CPU:
把以下操作放到子线程中:
1、对象创建、调整、销毁
2、预排版(布局计算、文本计算、缓存高度等)
3、预渲染(文本等异步绘制,图片解码等)
GPU:
纹理渲染,视图混合
预排版
布局计算、文本计算
以 TableView 为例,每次滑动屏幕的时候,cell内部控件Frame的计算,以及cell高度的计算等非常地频繁,如果布局非常复杂的话,那么大量的计算以及渲染会造成界面的卡顿。
缓存高度
将cell的内部控件的 Frame、Cell的高度提前计算出来,保存到 Model 中,这样就减少了非常多的计算量,卡顿的现象就会得到缓解。这个提前计算布局的方式就叫做预排版。
按需加载
监听tableview的滚动过程,当停止滚动的时候,开始加载,并且只加载可视范围内的cell。
预渲染
预渲染并不是GPU
渲染,而是将渲染的部分前期准备工作交给CPU
进行处理。
异步绘制
1、[self.layer.delegate displayLayer:]
2、代理负责生成对应的 bitmp
3、设置该 bitmap
作为该 layer.contents
属性的值。
预解码
图片加载流程
由于decode是在主线程进行的,而decode的过程是非常耗时的,因此预先进行decode可以加快图片的加载过程。
异步渲染
UIView
为CALayer提供内容,以及负责处理触摸等事件,参与响应链;
CALayer
负责显示内容contents,图形渲染。
异步渲染就是将渲染过程放到子线程当中去做。
drawrect 调用堆栈
位图的生成
1、layout 构建视图
2、displayer 绘制
3、prepare (core animation)
4、commit to render server
异步渲染框架 Graver
Graver 是一款高效的 UI 渲染框架,它以更低的资源消耗来构建十分流畅的 UI 界面。Graver 独创性的采用了基于绘制的视觉元素分解方式来构建界面,得益于此,该框架能让 UI 渲染过程变得更加简单、灵活。
避免离屏渲染
在OpenGL中,GPU有2种渲染方式
- On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作;
- Off-Screen Rendering: 离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
离屏渲染消耗性能的原因
- 需要创建新的缓冲区
- 离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕切换到离屏,等离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕。
GPU离屏渲染何时会触发?
- 1、圆角, 同时设置
layer.masksToBounds = YES、layer.cornerRadius > 0
- 2、遮罩 ,
layer.mask
- 3、阴影,
layer.shadowXXX
,如果设置了layer.shadowPath
就不会产生离屏渲染 - 4、光栅化 ,
layer.shouldRasterize = YES
检测离屏渲染和图层混合
打开模拟器,菜单选中debug,显示如下:
-
这里面的
Color Off-screen Rendered
选中,那么产生离屏渲染的图层会高亮变成黄色。 -
这里的
Color Blended Layers
选中,那么红色区域表示图层发生了混合
光栅化
光栅化是将几何数据经过一系列变换后最终转换为像素,从而呈现在显示设备上的过程,光栅化的本质是坐标变换、几何离散化。
我们使用UITableview和UICollectionView时,经常会遇到各个 Cell 的样式是一样的,这时候我们可以使用这个属性提高性能:
cell.layer.shouldRaasterize=YES;
cell.layer.rasterizationScale=[[UIScreen mainScreen] scale];