序言
一个应用的好坏很大程度上取决于使用是否流畅,所以明白造成应用卡顿的原因及解决思路显的至关重要。
一 检测的方案根据线程是否相关分为两大类:
执行耗时任务会导致CPU短时间无法响应其他任务,检测任务耗时来判断是否可能导致卡顿
由于卡顿直接表现为操作无响应,界面动画迟缓,检测主线程是否能响应任务来判断是否卡顿
与主线程相关的检测方案包括:
fps
ping
runloop
与主线程不相关的检测包括:
stack backtrace
msgSend observe
二 FPS 监测
通常情况下,屏幕会保持60hz/s
的刷新速度,每次刷新时会发出一个屏幕刷新信号
,CADisplayLink
允许我们注册一个与刷新信号同步的回调处理。可以通过屏幕刷新机制来展示fps
值:
方法一
附核心代码
@implementation ViewController {
UILabel *_fpsLbe;
CADisplayLink *_link;
NSTimeInterval _lastTime;
float _fps;
}
- (void)startMonitoring {
if (_link) {
[_link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_link invalidate];
_link = nil;
}
_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(fpsDisplayLinkAction:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)fpsDisplayLinkAction:(CADisplayLink *)link {
if (_lastTime == 0) {
_lastTime = link.timestamp;
return;
}
self.count++;
NSTimeInterval delta = link.timestamp - _lastTime;
if (delta < 1) return;
_lastTime = link.timestamp;
_fps = _count / delta;
NSLog(@"count = %d, delta = %f,_lastTime = %f, _fps = %.0f",_count, delta, _lastTime, _fps);
self.count = 0;
_fpsLbe.text = [NSString stringWithFormat:@"FPS:%.0f",_fps];
}
运行效果图
监听
count
值的改变
#pragma mark - observer
- (void)addObserver {
[self addObserver:self forKeyPath:@"count" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"count new = %@, old = %@",[change valueForKey:@"new"], [change valueForKey:@"old"]);
}
分析
1.通过打印,我们知道每次刷新时会发出一个屏幕刷新信号,则与刷新信号同步的回调方法`fpsDisplayLinkAction:`会调用,然后count加一。
2.每隔一秒,我们计算一次 `fps `值,用一个变量_lastTime记录上一次计算 fps 值的时间,然后将 count 的值除以时间间隔,就得到了 fps 的值,在将_lastTime重新赋值,_count置成零。
3.正常情况下,屏幕会保持60hz/s的刷新速度,所以1秒内`fpsDisplayLinkAction:`方法会调用60次。fps 计算的值为0,就不卡顿,流畅。
4.如果1秒内`fpsDisplayLinkAction:`只回调了50次,计算出来的fps就是 _count / delta(时间间隔) 。
方法二
核心代码
- (void)startFpsMonitoring {
_link = [CADisplayLink displayLinkWithTarget: self selector: @selector(displayFps:)];
[_link addToRunLoop: [NSRunLoop mainRunLoop] forMode: NSRunLoopCommonModes];
}
- (void)displayFps: (CADisplayLink *)fpsDisplay {
self.count++;
CFAbsoluteTime threshold = CFAbsoluteTimeGetCurrent() - _lastTime;
if (threshold >= 1.0) {
_fps = (_count / threshold);
_lastTime = CFAbsoluteTimeGetCurrent();
_fpsLbe.text = [NSString stringWithFormat:@"FPS:%.0f",_fps];
self.count = 0;
NSLog(@"count = %d,_lastTime = %f, _fps = %.0f",_count, _lastTime, _fps);
}
}
运行结果和方法一差不多。
结论
本文参考 质量监控-卡顿检测,感谢该作者。