一、卡顿问题的几种原因
复杂 ui 、图文混排的绘制量过大;
在主线程上做网络同步请求;
在主线程做大量的 io 操作;
运算量过大,cpu 持续高占用;
死锁和主子线程抢锁。
二、监测卡顿的思路
监测fps:
fps 是一秒显示的帧数,也就是一秒内画面变化数量。如果按照动画片来说,动画片的 fps 就是 24,是达不到 60 满帧的。也就是说,对于动画片来说,24 帧时虽然没有 60 帧时流畅,但也已经是连贯的了,所以并不能说 24 帧时就算是卡住了。 由此可见,简单地通过监视 fps 是很难确定是否会出现卡顿问题了,所以我就果断弃了通过监视 fps 来监控卡顿的方案。
runloop:
通过监控 runloop 的状态来判断是否会出现卡顿。runloop原理这里就不再多说,主要说方法,首先明确loop的状态有六个
typedef cf_options(cfoptionflags, cfrunloopactivity) { kcfrunloopentry , // 进入 loop kcfrunloopbeforetimers , // 触发 timer 回调 kcfrunloopbeforesources , // 触发 source0 回调 kcfrunloopbeforewaiting , // 等待 mach_port 消息 kcfrunloopafterwaiting ), // 接收 mach_port 消息 kcfrunloopexit , // 退出 loop kcfrunloopallactivities // loop 所有状态改变 }
我们需要监测的状态有两个:runloop 在进入睡眠之前和唤醒后的两个 loop 状态定义的值,分别是 kcfrunloopbeforesources 和 kcfrunloopafterwaiting ,也就是要触发 source0 回调和接收 mach_port 消息两个状态。
三、如何检查卡顿
说下步骤:
创建一个 cfrunloopobservercontext 观察者;
将创建好的观察者 runloopobserver 添加到主线程 runloop 的 common 模式下观察;
创建一个持续的子线程专门用来监控主线程的 runloop 状态;
一旦发现进入睡眠前的 kcfrunloopbeforesources 状态,或者唤醒后的状态 kcfrunloopafterwaiting,在设置的时间阈值内一直没有变化,即可判定为卡顿;
dump 出堆栈的信息,从而进一步分析出具体是哪个方法的执行时间过长;
上代码:
#import <foundation/foundation.h> @interface smlagmonitor : nsobject + (instancetype)shareinstance; - (void)beginmonitor; //开始监视卡顿 - (void)endmonitor; //停止监视卡顿 @end
#import "smlagmonitor.h" #import "smcallstack.h" #import "smcpumonitor.h" @interface smlagmonitor() { int timeoutcount; cfrunloopobserverref runloopobserver; @public dispatch_semaphore_t dispatchsemaphore; cfrunloopactivity runloopactivity; } @property (nonatomic, strong) nstimer *cpumonitortimer; @end @implementation smlagmonitor #pragma mark - interface + (instancetype)shareinstance { static id instance = nil; static dispatch_once_t dispatchonce; dispatch_once(&dispatchonce, ^{ instance = [[self alloc] init]; }); return instance; } - (void)beginmonitor { //监测 cpu 消耗 self.cpumonitortimer = [nstimer scheduledtimerwithtimeinterval:3 target:self selector:@selector(updatecpuinfo) userinfo:nil repeats:yes]; //监测卡顿 if (runloopobserver) { return; } dispatchsemaphore = dispatch_semaphore_create(0); //dispatch semaphore保证同步 //创建一个观察者 cfrunloopobservercontext context = {0,(__bridge void*)self,null,null}; runloopobserver = cfrunloopobservercreate(kcfallocatordefault, kcfrunloopallactivities, yes, 0, &runloopobservercallback, &context); //将观察者添加到主线程runloop的common模式下的观察中 cfrunloopaddobserver(cfrunloopgetmain(), runloopobserver, kcfrunloopcommonmodes); //创建子线程监控 dispatch_async(dispatch_get_global_queue(0, 0), ^{ //子线程开启一个持续的loop用来进行监控 while (yes) { long semaphorewait = dispatch_semaphore_wait(dispatchsemaphore, dispatch_time(dispatch_time_now, 20*nsec_per_msec)); if (semaphorewait != 0) { if (!runloopobserver) { timeoutcount = 0; dispatchsemaphore = 0; runloopactivity = 0; return; } //两个runloop的状态,beforesources和afterwaiting这两个状态区间时间能够检测到是否卡顿 if (runloopactivity == kcfrunloopbeforesources || runloopactivity == kcfrunloopafterwaiting) { // 将堆栈信息上报服务器的代码放到这里 //出现三次出结果 // if (++timeoutcount < 3) { // continue; // } nslog(@"monitor trigger"); dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_high, 0), ^{ // [smcallstack callstackwithtype:smcallstacktypeall]; }); } //end activity }// end semaphore wait timeoutcount = 0; }// end while }); } - (void)endmonitor { [self.cpumonitortimer invalidate]; if (!runloopobserver) { return; } cfrunloopremoveobserver(cfrunloopgetmain(), runloopobserver, kcfrunloopcommonmodes); cfrelease(runloopobserver); runloopobserver = null; } #pragma mark - private static void runloopobservercallback(cfrunloopobserverref observer, cfrunloopactivity activity, void *info){ smlagmonitor *lagmonitor = (__bridge smlagmonitor*)info; lagmonitor->runloopactivity = activity; dispatch_semaphore_t semaphore = lagmonitor->dispatchsemaphore; dispatch_semaphore_signal(semaphore); } - (void)updatecpuinfo { thread_act_array_t threads; mach_msg_type_number_t threadcount = 0; const task_t thistask = mach_task_self(); kern_return_t kr = task_threads(thistask, &threads, &threadcount); if (kr != kern_success) { return; } for (int i = 0; i < threadcount; i++) { thread_info_data_t threadinfo; thread_basic_info_t threadbaseinfo; mach_msg_type_number_t threadinfocount = thread_info_max; if (thread_info((thread_act_t)threads[i], thread_basic_info, (thread_info_t)threadinfo, &threadinfocount) == kern_success) { threadbaseinfo = (thread_basic_info_t)threadinfo; if (!(threadbaseinfo->flags & th_flags_idle)) { integer_t cpuusage = threadbaseinfo->cpu_usage / 10; if (cpuusage > 70) { //cup 消耗大于 70 时打印和记录堆栈 nsstring *restr = smstackofthread(threads[i]); //记录数据库中 // [[[smlagdb shareinstance] increasewithstackstring:restr] subscribenext:^(id x) {}]; nslog(@"cpu useage overload thread stack:\n%@",restr); } } } } } @end
使用,直接在app didfinishlaunchingwithoptions 方法里面这样写:
[[smlagmonitor shareinstance] beginmonitor];
以上就是ios利用runloop原理实现去监控卡顿实例详解的详细内容,更多关于ios runloop去监控卡顿的资料请关注代码网其它相关文章!
发表评论