【转】iOS实时卡顿监控

转自http://www.tanhao.me/code/151113.html/

在移动设备上开发软件,性能一直是我们最为关心的话题之一,我们作为程序员除了需要努力提高代码质量之外,及时发现和监控软件中那些造成性能低下的”罪魁祸首”也是我们神圣的职责.

众所周知,iOS平台因为UIKit本身的特性,需要将所有的UI操作都放在主线程执行,所以也造成不少程序员都习惯将一些线程安全性不确定的逻辑,以及其它线程结束后的汇总工作等等放到了主线,所以主线程中包含的这些大量计算、IO、绘制都有可能造成卡顿.

在Xcode中已经集成了非常方便的调试工具Instruments,它可以帮助我们在开发测试阶段分析软件运行的性能消耗,但一款软件经过测试流程和实验室分析肯定是不够的,在正式环境中由大量用户在使用过程中监控、分析到的数据更能解决一些隐藏的问题.

寻找卡顿的切入点

监控卡顿,最直接就是找到主线程都在干些啥玩意儿.我们知道一个线程的消息事件处理都是依赖于NSRunLoop来驱动,所以要知道线程正在调用什么方法,就需要从NSRunLoop来入手.CFRunLoop的代码是开源,可以在此处查阅到源代码http://opensource.apple.com/source/CF/CF-1151.16/CFRunLoop.c,其中核心方法CFRunLoopRun简化后的主要逻辑大概是这样的:

1234567891011121314151617181920212223
int32_t __CFRunLoopRun(){
    //通知即将进入runloop    __CFRunLoopDoObservers(KCFRunLoopEntry);
    do    {        // 通知将要处理timer和source
        __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
        __CFRunLoopDoObservers(kCFRunLoopBeforeSources);

        __CFRunLoopDoBlocks();  //处理非延迟的主线程调用
        __CFRunLoopDoSource0(); //处理UIEvent事件
                //GCD dispatch main queue
        CheckIfExistMessagesInMainDispatchQueue();
                // 即将进入休眠
        __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);

        // 等待内核mach_msg事件
        mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();
        // Zzz...

        // 从等待中醒来
        __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);

        // 处理因timer的唤醒
        if (wakeUpPort == timerPort)
            __CFRunLoopDoTimers();

        // 处理异步方法唤醒,如dispatch_async
        else if (wakeUpPort == mainDispatchQueuePort)
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()

        // UI刷新,动画显示
        else
            __CFRunLoopDoSource1();

        // 再次确保是否有同步的方法需要调用
        __CFRunLoopDoBlocks();
    } while (!stop && !timeout);

    //通知即将退出runloop
    __CFRunLoopDoObservers(CFRunLoopExit);}

不难发现NSRunLoop调用方法主要就是在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之间,还有kCFRunLoopAfterWaiting之后,也就是如果我们发现这两个时间内耗时太长,那么就可以判定出此时主线程卡顿.

量化卡顿的程度

要监控NSRunLoop的状态,我们需要使用到CFRunLoopObserverRef,通过它可以实时获得这些状态值的变化,具体的使用如下:

1234567891011121314151617
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){    MyClass *object = (__bridge MyClass*)info;    object->activity = activity;}

- (void)registerObserver{    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,                                                            kCFRunLoopAllActivities,                                                            YES,                                                            0,                                                            &runLoopObserverCallBack,                                                            &context);    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);}

只需要另外再开启一个线程,实时计算这两个状态区域之间的耗时是否到达某个阀值,便能揪出这些性能杀手.

为了让计算更精确,需要让子线程更及时的获知主线程NSRunLoop状态变化,所以dispatch_semaphore_t是个不错的选择,另外卡顿需要覆盖到多次连续小卡顿和单次长时间卡顿两种情景,所以判定条件也需要做适当优化.将上面两个方法添加计算的逻辑如下:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){    MyClass *object = (__bridge MyClass*)info;

    // 记录状态值    object->activity = activity;

    // 发送信号    dispatch_semaphore_t semaphore = moniotr->semaphore;    dispatch_semaphore_signal(semaphore);}

- (void)registerObserver{    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,                                                            kCFRunLoopAllActivities,                                                            YES,                                                            0,                                                            &runLoopObserverCallBack,                                                            &context);    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);

    // 创建信号    semaphore = dispatch_semaphore_create(0);

    // 在子线程监控时长    dispatch_async(dispatch_get_global_queue(0, 0), ^{        while (YES)        {            // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)            long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));            if (st != 0)            {                if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting)                {                    if (++timeoutCount < 5)                        continue;

                    NSLog(@"好像有点儿卡哦");                }            }            timeoutCount = 0;        }    });}

记录卡顿的函数调用

监控到了卡顿现场,当然下一步便是记录此时的函数调用信息,此处可以使用一个第三方Crash收集组件PLCrashReporter,它不仅可以收集Crash信息也可用于实时获取各线程的调用堆栈,使用示例如下:

12345678910
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);

当检测到卡顿时,抓取堆栈信息,然后在客户端做一些过滤处理,便可以上报到服务器,通过收集一定量的卡顿数据后经过分析便能准确定位需要优化的逻辑,至此这个实时卡顿监控就大功告成了!

文章示例代码下载:PerformanceMonitor.zip

时间: 2024-12-13 04:11:15

【转】iOS实时卡顿监控的相关文章

iOS:性能之卡顿检测

项目地址:https://github.com/tunsuy/iOSMonitorLag 该项目主要是针对ios项目的卡顿监控的探索,结合ios的运行机制和业界的实践,将其应用于公司项目中进行试运行,查看相关效果 二. 方案一 基于RunLoop 1. 背景 因为UIKit本身的特性,需要将所有的UI操作都放在主线程执行,所以也造成不少程序员都习惯将一些线程安全性不确定的逻辑,以及其它线程结束后的汇总工作等等放到了主线,所以主线程中包含的这些大量计算.IO.绘制都有可能造成卡顿. 在Xcode中

想让安卓app不再卡顿?看这篇文章就够了

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由likunhuang发表于云+社区专栏 实现背景 应用的使用流畅度,是衡量用户体验的重要标准之一.Android 由于机型配置和系统的不同,项目复杂App场景丰富,代码多人参与迭代历史较久,代码可能会存在很多UI线程耗时的操作,实际测试时候也会偶尔发现某些业务场景发生卡顿的现象,用户也经常反馈和投诉App使用遇到卡顿.因此,我们越来越关注和提升用户体验的流畅度问题. 已有方案 在这之前,我们将反馈的常见卡顿场景,或测试过程中常见的

分享的一个检查主线程卡顿的小工具

检查主线程卡顿的方法有两个 1.通过主线程runloop添加observer,观察runloop状态变化,不难发现NSRunLoop调用方法主要就是在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之间,还有kCFRunLoopAfterWaiting之后,也就是如果我们发现这两个时间内耗时太长,那么就可以判定出此时主线程卡顿. 主线程卡顿监控的例子 2.通过一个异步线程不断的往主线程里执行一个操作,如果一个duration之内执行不成功,则认为主线

iOS开发——项目实战总结&amp;UITableView性能优化与卡顿问题

UITableView性能优化与卡顿问题 1.最常用的就是cell的重用, 注册重用标识符 如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell 如果有很多数据的时候,就会堆积很多cell.如果重用cell,为cell创建一个ID 每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell 2.避免cell的重新布局 cell的布局填充等操作 比较耗时,一般创建时就布局好 如可以将cell单独放到一个自定义类,初始化时就布局好

iOS 如何解决UITableView刷新卡顿现象

对之前的项目总结一下: 事情是这样的,我需要在定制Cell完成新闻类app的展示 虽然说SDWebImage提供了一个方法,异步加载图片到UIImageView上 但是,有些时候.需要的图片并不是需要铺满UIImageView 于是就抛弃了,后来想想还是挺二逼的.我可以在下面在铺一层边框UIImageView也能达到这个效果 于是乎,故作高大上,非要自己写. 后来看别人的方法,找到点感觉,还是写出来了,不过还是建立在网络良好的情况下 解决办法 主要要做到一下几个方面: 1.除了UI部分,所有的加

关于移动端开发时iOS上滑屏卡顿的问题,以及电话类数字的样式失控问题

写在前面的话: tips:写移动的时候,那些头部需要固定显示在显示屏顶部的,通常在PC端我会用fixed来写.但是,在移动端,这并不是一个好方法,因为弹出输入小键盘的时候,会造成fixed 的元素偏移掉,在这里有两种方法可以解决: 1.建议移动端布局采用以下方式(见正文),如果有错误的地方,还请指正~ 2.另外还看到一篇文章提到这个问题,作者让固定的头部仍然采用fixed, 然后内容区也用了fixed,内容区的fixed元素这样写:{position:fixed;top:80px;bottom:

解决页面使用overflow: scroll,overflow-y:hidden在iOS上滑动卡顿的问题

解决页面使用overflow: scroll,overflow-y:hidden在iOS上滑动卡顿的问题 div{ width: 100%; overflow-y: hidden; -webkit-overflow-scrolling: touch; } 在使用overflow的的地方加上?-webkit-overflow-scrolling: touch;便可解决页面在ios机器上卡顿的问题. 解决由-webkit-overflow-scrolling: touch 引起的ios滚动条(将滚动

iOS开发之记一次App卡顿Bug的解决历程(踩了一个StoryBoard的坑)

虽然今天是周末,但是还是要学习的不是.写这篇博客的目的呢是记录一下自己在上次项目迭代中踩的坑,不过这个坑已经填上了.虽然坑不大,但是踩上去肯定能崴脚.其实还是那句话,在没人给你指路的情况下,踩的坑多了,慢慢的就成长了.为了填今天要讲的这个坑,午觉都没睡呢.当然今天博客的内容并不高深,而且出现的几率还是蛮大的,所以喽就记录一下.也许你已经踩过,或者你已经将此坑填上,但是今天是我踩了一脚呢,没办法,还是记录一下吧. 解决历程用一个字描述就是:“删”. 一.描述这个“坑” 首先呢,我们先来看一下这个B

iOS 添加阴影后 屏幕卡顿 抖动

- (void)awakeFromNib { // Initialization code _btnViews.layer.shadowPath =[UIBezierPath bezierPathWithRect:_btnViews.bounds].CGPath; _btnViews.layer.shadowColor = [UIColor grayColor].CGColor;//shadowColor阴影颜色 _btnViews.layer.shadowOffset = CGSizeMake