1.有哪些原因会导致App卡顿?
- 复杂 UI 、图文混排的绘制量过大;
- 在主线程上做网络同步请求;
- 在主线程做大量的 IO 操作;
- 运算量过大,CPU 持续高占用;
- 死锁和主子线程抢锁。
CPU
- 对象创建
- 对象调整
- 对象销毁
- 布局计算
- Autolayout
- 文本计算
- 文本渲染
- 图片解码
- 图像绘制
GPU
- 纹理渲染
- 视图的混合
- 图形生成
2.卡顿监测办法?
- FPS
通常情况下,屏幕会保持60hz/s的刷新速度,每次刷新时会发出一个屏幕刷新信号,CADisplayLink允许我们注册一个与刷新信号同步的回调处理。可以通过屏幕刷新机制来展示fps值:
- (void)startFpsMonitoring {
WeakProxy *proxy = [WeakProxy proxyWithClient: self];
self.fpsDisplay = [CADisplayLink displayLinkWithTarget: proxy selector: @selector(displayFps:)];
[self.fpsDisplay addToRunLoop: [NSRunLoop mainRunLoop] forMode: NSRunLoopCommonModes];
}
- (void)displayFps: (CADisplayLink *)fpsDisplay {
_count++;
CFAbsoluteTime threshold = CFAbsoluteTimeGetCurrent() - _lastUpadateTime;
if (threshold >= 1.0) {
[FPSDisplayer updateFps: (_count / threshold)];
_lastUpadateTime = CFAbsoluteTimeGetCurrent();
}
}
- Ping
ping是一种常用的网络测试工具,用来测试数据包是否能到达ip地址。在卡顿发生的时候,主线程会出现短时间内无响应这一表现,基于ping的思路从子线程尝试通信主线程来获取主线程的卡顿延时:
@interface PingThread : NSThread
......
@end
@implementation PingThread
- (void)main {
[self pingMainThread];
}
- (void)pingMainThread {
while (!self.cancelled) {
@autoreleasepool {
dispatch_async(dispatch_get_main_queue(), ^{
[_lock unlock];
});
CFAbsoluteTime pingTime = CFAbsoluteTimeGetCurrent();
NSArray *callSymbols = [StackBacktrace backtraceMainThread];
[_lock lock];
if (CFAbsoluteTimeGetCurrent() - pingTime >= _threshold) {
......
}
[NSThread sleepForTimeInterval: _interval];
}
}
}
@end
- RunLoop
作为和主线程相关的最后一个方案,基于runloop的检测和fps的方案非常相似,都需要依赖于主线程的runloop。由于runloop会调起同步屏幕刷新的callback,如果loop的间隔大于16.67ms,fps自然达不到60hz。而在一个loop当中存在多个阶段,可以监控每一个阶段停留了多长时间:
- (void)startRunLoopMonitoring {
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if (CFAbsoluteTimeGetCurrent() - _lastActivityTime >= _threshold) {
......
_lastActivityTime = CFAbsoluteTimeGetCurrent();
}
});
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}
3.记录卡顿时的调用栈
监控到了卡顿现场,当然下一步便是记录此时的函数调用信息,此处可以使用一个第三方Crash收集组件PLCrashReporter,它不仅可以收集Crash信息也可用于实时获取各线程的调用堆栈,使用示例如下:
PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD
symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];
PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];
NSData *data = [crashReporter generateLiveReport];
PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];
NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter
withTextFormat:PLCrashReportTextFormatiOS];
NSLog(@"------------\n%@\n------------", report);
当检测到卡顿时,抓取堆栈信息,然后在客户端做一些过滤处理,便可以上报到服务器,通过收集一定量的卡顿数据后经过分析便能准确定位需要优化的逻辑,至此这个实时卡顿监控就大功告成了。
参考链接:
iOS 保持界面流畅的技巧
网友评论