美文网首页
ios-界面卡顿监测

ios-界面卡顿监测

作者: Harry__Li | 来源:发表于2022-01-25 16:26 被阅读0次

现在对于app的要求越来越高,并不是像以前我们只要把功能需求做出来就行。还要求我们对app做各种优化,写出高质量的代码。所以对于界面的流畅度的监测就非常有必要了。那应该如何监测呢?

FPS的监测
  • 原理:CADisplayLink是一个特殊的定时器,它的调用执行和GPU的渲染有关,我们常常说的60刷和120hz刷屏。也就以为这,默认情况下CADisplayLink每秒会调用60次或者120次。我们就可以计算它在一秒内调用了多少次,来看界面的流畅度。正常情况下50<fps 肉眼看不出来卡顿,55以上就很流畅,说明你已经做的很好了。下面我们来看代码,具体是如何实现的。
//初始化创建
CADisplayLink *link=[CADisplayLink displayLinkWithTarget:self selector:@selector(displayAction:)];
//添加到主线程中去
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

//通记录回调次数和相隔时间 计算fps
-(void)displayAction:(CADisplayLink *)link{
    //记录上次的时间
    static NSTimeInterval lastTime = 0;
    //记录回调了多少次
    static NSInteger frameCount = 0;
    if (lastTime==0) {
        lastTime=link.timestamp;
        return;
    }
    frameCount++;
    NSTimeInterval duration=link.timestamp-lastTime;
    //刷新间隔1秒
    if (duration>=1) {
        NSInteger fps=frameCount/duration;
        frameCount=0;
        lastTime=link.timestamp;
    }
}
  • 优缺点:通过上面的代码,可以看到界面帧率。越小说明界面越不流畅。但它的作用也仅仅只是这样了。即使知道了卡顿,我们还要自己去查找,无法快速的定位到底是那个方法引起的卡顿。
RunLoop监听应用程序卡顿

在前面我们讲过runloop那个非常烂大街的流程图,这里就不放了。
KCFRunLoopEntyr开始runloop-->timer--> kCFRunLoopBeforeSources(开始处理事件)-->kCFRunLoopBeforeWaiting(事件处理完了,开始休眠)--> kCFRunLoopAfterWaiting(被叫醒)-->然后开始回到最开的步骤 重新实现流程
原理:创建一个子线程进行循环监测,每次检测时设置为YES,然后派发任务切换到主线程,将标记位置设置为NO,如果没有设置成NO,说明主线程卡顿了。关键代码如下:

#import <Foundation/Foundation.h>
#define SHAREDMONITOR [LXDAppFluecyMonitor sharedMonitor]
/*!
 *  @brief  监听UI线程卡顿
 */
@interface LXDAppFluecyMonitor : NSObject
+ (instancetype)sharedMonitor;
- (void)startMonitoring;
- (void)stopMonitoring;
@end
#import "LXDAppFluecyMonitor.h"

#define LXD_DEPRECATED_POLLUTE_MAIN_QUEUE

@interface LXDAppFluecyMonitor ()
@property (nonatomic, assign) int timeOut;
@property (nonatomic, assign) BOOL isMonitoring;
@property (nonatomic, assign) CFRunLoopObserverRef observer;
@property (nonatomic, assign) CFRunLoopActivity currentActivity;
@property (nonatomic, strong) dispatch_semaphore_t semphore;
@property (nonatomic, strong) dispatch_semaphore_t eventSemphore;
@end

#define LXD_SEMPHORE_SUCCESS 0
static NSTimeInterval lxd_restore_interval = 5;
static NSTimeInterval lxd_time_out_interval = 1;
static int64_t lxd_wait_interval = 200 * NSEC_PER_MSEC;

/*!
 *  @brief  监听runloop状态在after waiting和before sources之间
 */
static inline dispatch_queue_t lxd_fluecy_monitor_queue() {
    static dispatch_queue_t lxd_fluecy_monitor_queue;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        lxd_fluecy_monitor_queue = dispatch_queue_create("com.sindrilin.lxd_monitor_queue", NULL);
    });
    return lxd_fluecy_monitor_queue;
}

#define LOG_RUNLOOP_ACTIVITY 0
static void lxdRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void * info) {
    SHAREDMONITOR.currentActivity = activity;
    dispatch_semaphore_signal(SHAREDMONITOR.semphore);
#if LOG_RUNLOOP_ACTIVITY
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"runloop entry");
            break;
        case kCFRunLoopExit:
            NSLog(@"runloop exit");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"runloop after waiting");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"runloop before timers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"runloop before sources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"runloop before waiting");
            break;
        default:
            break;
    }
#endif
};

@implementation LXDAppFluecyMonitor

#pragma mark - Singleton override
+ (instancetype)sharedMonitor {
    static LXDAppFluecyMonitor * sharedMonitor;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        sharedMonitor = [[super allocWithZone: NSDefaultMallocZone()] init];
        [sharedMonitor commonInit];
    });
    return sharedMonitor;
}

+ (instancetype)allocWithZone: (struct _NSZone *)zone {
    return [self sharedMonitor];
}

- (void)dealloc {
    [self stopMonitoring];
}

- (void)commonInit {
    self.semphore = dispatch_semaphore_create(0);
}
#pragma mark - Public
- (void)startMonitoring {
 if (_isMonitoring) { return; }
    _isMonitoring = YES;
    CFRunLoopObserverContext context = {
        0,
        (__bridge void *)self,
        NULL,
        NULL
    };
    //创建监听者
    _observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &lxdRunLoopObserverCallback, &context);
    //监听主RunLoop
    CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
dispatch_async(lxd_event_monitor_queue(), ^{
    NSLog(@"%@",[NSThread currentThread]);
    while (SHAREDMONITOR.isMonitoring) {
        //主线程的RunLoop 即将进入休眠
        if (SHAREDMONITOR.currentActivity == kCFRunLoopBeforeWaiting) {
            //默认超时
            __block BOOL timeOut = YES;
            NSLog(@"0");
            
            dispatch_async(dispatch_get_main_queue(), ^{
                //切换到主线程 执行任务
                //若主线程没有出现卡顿 能正常执行任务 将timeOut设置为NO
                //若主线程出现卡顿 不能能正常执行任务
               timeOut = NO;
                //发送信号量 +1
               dispatch_semaphore_signal(SHAREDMONITOR.eventSemphore);
               NSLog(@"1");
            });
            
            NSLog(@"2");
            //当前子线程休眠1秒钟
            [NSThread sleepForTimeInterval: lxd_time_out_interval];
            NSLog(@"3");
            //超时打印函数调用栈
            if (timeOut) {
               NSLog(@"4");
               [LXDBacktraceLogger lxd_logMain];
            }
            NSLog(@"5");
            //释放信号量 -1 此时的信号量为-1<0 下面的逻辑不会执行 循环依然执行
            dispatch_wait(SHAREDMONITOR.eventSemphore, DISPATCH_TIME_FOREVER);
            NSLog(@"6");
        }
    }
});
}
- (void)stopMonitoring {
    if (!_isMonitoring) { return; }
    _isMonitoring = NO;
    
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    CFRelease(_observer);
    _observer = nil;
}
@end

上面一堆的代码,主要实现的逻辑方法在startMonitoring中

  • 1、首先判断_isMonitoring,当_isMonitoring为YES时,说明主线程一直处于卡顿状态。
  • 2、_isMonitoring为NO时,表示已派发任务到主线程。这时我们把_isMonitoring设置为YES。
    -3、 创建监听这,监听主runloop。
  • 4、创建子线程lxd_fluecy_monitor_queue。while判断_isMonitoring,为0,表示在指定时间内接收到主线程发出的信号;非0,表示在指定时间内没有接收到主线程发出的信号,主线程可能在执行耗时任务,有可能造成应用程序的卡顿
    -5、判断主runloop即将进入休眠,这时候切换到主线程 执行任务。同时我们让当前的子线程休眠一秒。这里为什么要休眠呢?这时因为,切换到主线程 设置timeout为NO,如果在我们设置的时间内能切换成功,就判断他不卡顿。如若1秒以后timeout还是yes,证明主线程在执行耗时操作。没有办法去执行 timeOut = NO;。这个时候我们就要记录卡顿的日志[LXDBacktraceLogger lxd_logMain];
    -6.最后 信号量也没办法释放,他会一直等待主线程切换执行,发出信号成功以后才能释放。
使用Instrument工具实时监测App

使用Time Profiler
修改项目为debug模式,然后如下图设置


截屏2022-01-25 下午4.18.57.png

按步骤打开product-->profile-->time


截屏2022-01-25 下午4.24.13.png

设置call tree,显示代码位置。

以上就完成了界面卡顿监测。

相关文章

  • ios-界面卡顿监测

    现在对于app的要求越来越高,并不是像以前我们只要把功能需求做出来就行。还要求我们对app做各种优化,写出高质量的...

  • iOS界面卡顿监测

    RunLoop RunLoop是与线程相关的概念。 在Cocoa和Core Foundation框架中提供了run...

  • iOS:卡顿延迟监测、界面启动耗时、界面FPS

    卡顿延迟监测 App在线运行的时候发生了卡顿,是很难定位卡顿原因的。 一般界面卡顿原因:1.死锁:主线程拿到锁 A...

  • iOS卡顿监测方案总结

    iOS卡顿监测方案总结iOS卡顿监测方案总结

  • iOS版界面卡顿监测方案

    实现思路在开始之前,我们先思考一下,界面卡顿是由哪些原因导致的? 1.死锁:主线程拿到锁 A,需要获得锁 B,而同...

  • 21-性能优化

    一、CPU和GPU 二、卡顿产生的原因和优化 卡顿优化-CPU 卡顿优化-GPU 卡顿监测 监控卡顿的demo:推...

  • iOS性能优化之界面卡顿监测

    1.使用Instruments监测卡顿 选择Animation Hitches 最后分析解决问题:找到罪魁祸首sh...

  • iOS通过runloop监控卡顿

    质量监控-卡顿检测iOS实时卡顿监控基于Runloop简单监测iOS卡顿的demo微信iOS卡顿监控系统iOS-R...

  • Android性能优化-App卡顿

    目录 1.卡顿简介 2.检测Jank:介绍监测卡顿的方法 3.修复卡顿问题:介绍如何修复卡顿问题; 4.卡顿通常的...

  • 界面卡顿

    记单词 FPS:Frames Per Second每秒传输帧数。60满帧。每16ms刷新一次屏幕。 VSync:v...

网友评论

      本文标题:ios-界面卡顿监测

      本文链接:https://www.haomeiwen.com/subject/jiywhrtx.html