本文是鉴戒 戴铭老师 iOS开辟高手课 内容总结。
目次
1、卡顿题目
2、RunLoop先容
3、RunLoop实行过程 先容
4、RunLoop全部六个状态
5、RunLoop监控卡顿操纵
6、直接用 PLCrashReporter这个开源的第三方库来获取堆栈信息
7、微信开源 matrix-ios卡顿监控 工具
8、腾讯 Bugly 工具 Bugly : 可监控 App在运行过程中发生的 【瓦解、卡顿、ANR、错误】
总结监控卡顿Demo:Demo
1、卡顿题目:卡顿题目,就是在主线程上无法相应用户交互的题目。假如一个 App 时不时地就给你卡一下,偶然还长时间无相应
1、卡顿根源:
1>复杂 UI 、图文混排的绘制量过大;
2>在主线程上做网络同步哀求;
3>在主线程做大量的 IO 操纵;
4>运算量过大,CPU 一连高占用;
5>死锁和主子线程抢锁。
2、FPS:FPS 是一秒体现的帧数,也就是一秒内画面变革数目。假如按照动画片来说,动画片的 FPS 就是 24,是达不到 60 满帧的。也就是说,对于动画片来说,24 帧时固然没有 60 帧时流畅,但也已经是连贯的了,以是并不能说 24 帧时就算是卡住了。由此可见,简单地通过监督 FPS 是很难确定是否会出现卡顿题目了。
2、RunLoop先容(推荐的监控卡顿的方案是:通过监控 RunLoop 的状态来判定是否会出现卡顿。)
1、RunLoop原理:对于 iOS 开辟来说,监控卡顿就是要去找到主线程上都做了哪些事儿。我们都知道,线程的消息变乱是依赖于 NSRunLoop 的,以是从 NSRunLoop 入手,就可以知道主线程上都调用了哪些方法。我们通过【监听 NSRunLoop 的状态,就可以或许发现调用方法是否实行时间过长】,从而判定出是否会出现卡顿。
2、RunLoop 是 iOS 开辟中的一个基础概念,它可以做哪些事儿,以及它为什么可以做成这些事儿?
RunLoop 这个对象,在 iOS 里由 CFRunLoop 实现。
【简单来说,RunLoop 是用来监听输入源,举行调治处理惩罚的】。这里的输入源可以是输入装备、网络、周期性大概耽误时间、异步回调。
RunLoop 会吸收两种范例的输入源:一种是来自另一个线程大概来自差异应用的异步消息;另一种是来自预订时间大概重复隔断的同步变乱。
【RunLoop 的目标是,当有变乱要行止理时保持线程忙,当没有变乱要处理惩罚时让线程进入休眠。】
以是,相识 RunLoop 原理不但可以或许运用到监控卡顿上,还可以进步用户的交互体验。通过将那些繁重而不告急会大量占用 CPU 的任务(好比图片加载),放到空闲的 RunLoop 模式里实行,就可以避开在 UITrackingRunLoopMode 这个 RunLoop 模式时是实行。UITrackingRunLoopMode 是用户举行滚动操纵时会切换到的 RunLoop 模式,制止在这个 RunLoop 模式实行繁重的 CPU 任务,就能制止影响用户交互操纵上体验。
3、RunLoop实行过程 先容
1、第一步关照 observers:RunLoop 要开始进入 loop 了。紧接着就进入 loop
2、第二步开启一个 do while 来保活线程。关照 Observers:RunLoop 会触发 Timer 回调、Source0 回调,接着实行参加的 block。代码如下:
接下来,触发 Source0 回调,假如有 Source1 是 ready 状态的话,就会跳转到 handle_msg 行止理消息。代码如下:
3、第三步回调触发后,关照 Observers:RunLoop 的线程将进入休眠(sleep)状态。代码如下:
4、第四步进入休眠后,会期待 mach_port 的消息,以再次叫醒。只有在下面四个变乱出现时才会被再次叫醒:
1>基于 port 的 Source 变乱;
2>Timer 时间到;
3>RunLoop 超时;
4>被调用者叫醒。
期待叫醒的代码如下:
5、第五步叫醒时关照 Observer:RunLoop 的线程刚刚被叫醒了。代码如下:
6、第六步RunLoop 被叫醒后就要开始处理惩罚消息了:
1>假如是 Timer 时间到的话,就触发 Timer 的回调;
2>假如是 dispatch 的话,就实行 block;
3>假如是 source1 变乱的话,就处理惩罚这个变乱。消息实行完后,就实行加到 loop 里的 block。代码如下:
7、第七步根据当前 RunLoop 的状态来判定是否需要走下一个 loop。当被外部逼迫制止或 loop 超时时,就不继承下一个 loop 了,否则继承走下一个 loop 。代码如下:
整个 RunLoop 过程,我们可以总结为如下所示的一张图片。RunLoop全部代码过程
4、RunLoop全部六个状态
loop 的六个状态通过对 RunLoop 原理的分析,我们可以看出在整个过程中,loop 的状态包括 6 个,其代码界说如下:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry , // 进入 loop
kCFRunLoopBeforeTimers , // 触发 Timer 回调
kCFRunLoopBeforeSources , // 触发 Source0 回调
kCFRunLoopBeforeWaiting , // 期待 mach_port 消息
kCFRunLoopAfterWaiting , // 吸收 mach_port 消息
kCFRunLoopExit , // 退出 loop
kCFRunLoopAllActivities // loop 全部状态改变}
分析:假如 RunLoop 的线程,【进入就寝火线法的实行时间过长而导致无法进入就寝】,大概 【线程叫醒后吸收消息时间过长而无法进入下一步的话】,就可以认为是 ——> 线程受阻了。【假如这个线程是主线程的话,体现出来的就是出现了卡顿】。
假如我们要使用 RunLoop 原理来监控卡顿的话,就是要关注这两个阶段。RunLoop 在进入就寝之前和叫醒后的两个 loop 状态界说的值,分别是 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting ,也就是要触发 Source0 回调和吸收 mach_port 消息两个状态。
5、RunLoop监控卡顿操纵 (参考资料)、腾讯matirx 框架 matirx 、 大概 Gitee堆栈Matrix
1、开启一个子线程监控的代码如下:代码中的 NSEC_PER_SEC,代表的是触发卡顿的时间阈值,单位是秒。可以看到,我们把这个阈值设置成了 3 秒。那么,这个 3 秒的阈值是从何而来呢?如许设置公道吗?着实,触发卡顿的时间阈值,我们可以根据 WatchDog 机制来设置。WatchDog 在差异状态下设置的差异时间,如下所示:启动(Launch):20s;规复(Resume):10s;挂起(Suspend):10s;退出(Quit):6s;后台(Background):3min(在 iOS 7 之前,每次申请 10min; 之后改为每次申请 3min,可一连申请,最多申请到 10min)。通过 WatchDog 设置的时间,我认为可以把启动的阈值设置为 10 秒,其他状态则都默认设置为 3 秒。总的原则就是,要小于 WatchDog 的限定时间。固然了,这个阈值也不消小得太多,原则就是要优先办理用户感知最显着的体验题目。
2、怎样获取卡顿的方法堆栈信息?
子线程监控发现卡顿后,还需要记载当前出现卡顿的方法堆栈信息,并适时推送到服务端供开辟者分析,从而办理卡顿题目。那么,在这个过程中,怎样获取卡顿的方法堆栈信息呢?
获取堆栈信息的一种方法是直接调用体系函数。
这种方法的长处在于,性能斲丧小。但是,它只可以或许获取简单的信息,也没有办法共同 dSYM 来获取具体是哪行代码出了题目,而且可以或许获取的信息范例也有限。这种方法,由于性能比力好,以是实用于观察大盘统计卡顿情况,而不是想要找到卡顿缘故原由的场景。
直接调用体系函数方法的告急思绪是:用 signal 举行错误信息的获取
6、直接用 PLCrashReporter这个开源的第三方库来获取堆栈信息
具体怎样使用 PLCrashReporter 来获取堆栈信息,代码如下所示:
// 获取数据
NSData *lagData = [[[PLCrashReporter alloc] initWithConfiguration:[[PLCrashReporterConfig alloc] initWithSignalHandlerTypeLCrashReporterSignalHandlerTypeBSD symbolicationStrategyLCrashReporterSymbolicationStrategyAll]] generateLiveReport];
// 转换成 PLCrashReport 对象
PLCrashReport *lagReport = [[PLCrashReport alloc] initWithData:lagData error:NULL];
// 举行字符串格式化处理惩罚
NSString *lagReportString = [PLCrashReportTextFormatter stringValueForCrashReport:lagReport withTextFormatLCrashReportTextFormatiOS];
//将字符串上传服务器NSLog(@"lag happen, detail below: \n %@",lagReportString);
7、微信开源 matrix-ios卡顿监测 https://github.com/tencent/matrix/tree/master/matrix/matrix-iOS https://github.com/Tencent/matrix 工具
微信团队就放出了一篇文章专门先容卡顿监控方案“微信 iOS 卡顿监控体系”链接。之后,很多团队参照这篇文章开辟了自己的卡顿监控体系。
1> 本年的 4 月 3 号,微信团队将他们的卡顿监控体系matrix开源出来了,包括 Matrix for iOS / MacOS https://github.com/Tencent/matrix/tree/master/matrix/matrix-iOS 和Android体系的监控方案。关于 matrix-iOS 的卡顿监控原理,你可以点击这个链接 https://github.com/Tencent/matrix/wiki/Matrix-for-iOS-macOS-卡顿监控原理 查察。
假如你的 App 现在还没有卡顿监控体系,可以思量直接集成 matrix-iOS,直接在 Podfile 里添加 pod ‘matrix-wechat’ 就可以了。假如已经有了卡顿监控体系,我发起你阅读下 matrix-iOS 的代码,里面有很多细节值得我们学习。
好比:子线程监控检测时间隔断:matrix-iOS 监控卡顿的子线程是通过 NSThread 创建的,检测时间隔断正常情况是 1 秒,在出现卡顿情况下,隔断时间会受检测线程退火算法影响,按照斐波那契数列递增,直到没有卡顿时规复为 1 秒。
2> 子线程监控退火算法:制止一个卡顿会写入多个文件的情况。
【为了低落检测带来的性能消耗,我们为检测线程增长了退火算法:
每次子线程查抄到主线程卡顿,会先得到主线程的堆栈并生存到内存中(不会直接去得到线程快照生存到文件中);
将得到的主线程堆栈与前次卡顿得到的主线程堆栈举行比对:假如堆栈差异,则得到当前的线程快照并写入文件中;
假如类似则会跳过,并按照斐波那契数列将查抄时间递增直到没有碰到卡顿大概主线程卡顿堆栈不一样。
如许,可以制止同一个卡顿写入多个文件的情况;制止检测线程碰到主线程卡死的情况下,不绝写线程快照文件。】
3> RunLoop 卡顿时间阈值设置:对于 RunLoop 超时阈值的设置,微信设置的是 2 秒。CPU 使用率阈值设置:当单核 CPU 使用率高出 80%,就判定 CPU 占用过高。CPU 使用率过高,大概导致 App 卡顿。
【Matrix 卡顿监控在 Runloop 的起始 最开始和竣事最末端 位置添加 Observer,从而得到主线程的开始和竣事状态。卡顿监控起一个子线程定时查抄主线程的状态,当主线程的状态运行高出肯定阈值则认为主线程卡顿,从而标记为一个卡顿。现在微信使用的卡顿监控,主步伐 Runloop 超时的阈值是 2 秒,子线程的查抄周期是 1 秒。每隔 1 秒,子线程查抄主线程的运行状态;假如查抄到主线程 Runloop 运行高出 2 秒则认为是卡顿,并得到当前的线程快照。同时,我们也认为 CPU 过高也大概导致应用出现卡顿,以是在子线程查抄主线程状态的同时,假如检测到 CPU 占用过高,会捕捉当前的线程快照生存到文件中。现在微信应用中认为,单核 CPU 的占用高出了 80%,此时的 CPU 占用就过高了。】
4> 线程过多时 CPU 在切换线程上下文时,还会更新寄存器,更新寄存器时需要寻址,而寻址的过程还会有较大的 CPU 斲丧。按照微信团队的履历,线程数超出 64 个时会导致主线程卡顿,假如卡顿是由于线程多造成的,那么就没必要通过获取主线程堆栈去找卡顿缘故原由了。根据 matrix-iOS 的实测,每隔 50 毫秒获取主线程堆栈会增长 3% 的 CPU 占用,以是当检测到主线程卡顿以后,我们需要先判定是否是由于线程数过多导致的,而不是一有卡顿题目就去获取主线程堆栈。
|