导致卡顿的原因
- 复杂 UI 、图文混排的绘制量过大
- 在主线程上做网络同步请求
- 在主线程做大量的 IO 操作
- 运算量过大,CPU 持续高占用
- 死锁和主子线程抢锁
原理
监控卡顿就是找到主线程都做了哪些事。线程的消息事件是依赖于 NSRunLoop 的。我们通过监听 NSRunLoop 的状态,能够发现调用方法是否执行事件过长,从而判断是否会出现卡顿。
RunLoop 会接受到两种输入源,一种是来自另一个线程或者来自不同应用到异步消息,另一种是来自预定时间或者重复间隔到同步事件。RunLoop 的目的是,当有事件要去处理时保持线程忙,当没有事件要处理时让线程进入休眠。
Runloop 的六个状态
1 2 3 4 5 6 7 8 9
| typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry , kCFRunLoopBeforeTimers , kCFRunLoopBeforeSources , kCFRunLoopBeforeWaiting , kCFRunLoopAfterWaiting ), kCFRunLoopExit , kCFRunLoopAllActivities }
|
如果 RunLoop 的线程,进入睡眠前方法的执行时间过长而导致无法进入睡眠,或者睡眠唤醒后接受消息时间过长而无法进入下一步的话,就可以认为是线程受阻了。如歌这个线程主线程的话,表现出来的就是出现了卡顿。
所以需要关注两个阶段。RunLoop 在进入睡眠前和唤醒后的两个 loop 状态定义的值,分别是 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting,也就是触发 Source0 回调和接受 mach_port 消息两个状态。
具体实现
创建观察者 CFRunLoopObserverContext
1 2
| CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
|
将观察者 runLoopObserver 添加到主线程 RunLoop 到 common 模式下观察。然后创建一个持续到子线程专门用来监控主线程到 RunnLoop 状态。
一旦发现进入睡眠前的 kCFRunLoopBeforeSources 状态,或者睡眠后的状态 kCFRunLoopAfterWaiting,在设置的时间阈值内一直没有变化,即可判定为卡顿。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (YES) { long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC)); if (semaphoreWait != 0) { if (!runLoopObserver) { timeoutCount = 0; dispatchSemaphore = 0; runLoopActivity = 0; return; } if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) { } } timeoutCount = 0; } });
|
获取卡顿的方法堆栈信息
直接调用系统函数
优点:性能消耗小。但只能获取简单但信息,也没办法配合 dSYM 来获取具体哪行代码出了问题,而且能够获取的信息类型也有限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| static int s_fatal_signals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP, SIGTERM, SIGKILL, };
static int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]);
void UncaughtExceptionHandler(NSException *exception) { NSArray *exceptionArray = [exception callStackSymbols]; NSString *exceptionReason = [exception reason]; NSString *exceptionName = [exception name]; }
void SignalHandler(int code) { NSLog(@"signal handler = %d",code); }
void InitCrashReport() { for (int i = 0; i < s_fatal_signal_num; ++i) { signal(s_fatal_signals[i], SignalHandler); }
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); }
int main(int argc, char * argv[]) { @autoreleasepool { InitCrashReport(); return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
使用 PLCrashReporter 开源第三方库获取堆栈信息
能定位到具体代码,性能消耗也不大。
1 2 3 4 5 6 7 8 9
| NSData *lagData = [[[PLCrashReporter alloc] initWithConfiguration:[[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]] generateLiveReport];
PLCrashReport *lagReport = [[PLCrashReport alloc] initWithData:lagData error:NULL];
NSString *lagReportString = [PLCrashReportTextFormatter stringValueForCrashReport:lagReport withTextFormat:PLCrashReportTextFormatiOS];
NSLog(@"lag happen, detail below: \n %@",lagReportString);
|