如何检测APP的卡顿

监控APP卡顿的三种方式

  • FPS
  • 子线程ping主线程
  • Runloop

FPS检测APP卡顿

通常情况下,屏幕会保持60hz/s的刷新速度,每次刷新时会发出一个屏幕刷新信号,CADisplayLink允许我们注册一个与刷新信号同步的回调处理。可以通过屏幕刷新机制来展示fps值:

image.png

显示器中是固定的频率,比如iOS中是每秒60帧(60FPS),即每帧16.7ms。从上图中可以看出,每两个VSync信号之间有时间间隔(16.7ms),在这个时间内,CPU主线程计算布局,解码图片,创建视图,绘制文本,计算完成后将内容交给GPU,GPU变换,合成,渲染,放入帧缓冲区。假如16.7ms内,CPU和GPU没有来得及生产出一帧缓冲,那么这一帧会被丢弃,显示器就会保持不变,继续显示上一帧内容,这就将导致导致画面卡顿。所以无论CPU, GPU,哪个消耗时间过长,都会导致在16.7ms内无法生成一帧缓存

主线程为了达到接近60fps的绘制效率,不能在UI线程有单个超过(1/60s≈16ms)的计算任务,否则就会卡顿

  • 死锁:主线程拿到锁 A,需要获得锁 B,而同时某个子线程拿了锁 B,需要锁 A,这样相互等待就死锁了。
  • 抢锁:主线程需要访问 DB,而此时某个子线程往 DB 插入大量数据。通常抢锁的体验是偶尔卡一阵子,过会就恢复了。
  • 主线程大量 IO:主线程为了方便直接写入大量数据,会导致界面卡顿。
  • 主线程大量计算:算法不合理,导致主线程某个函数占用大量 CPU。
  • 大量的 UI 绘制:复杂的 UI、图文混排等,带来大量的 UI 绘制。

我们可以用CADisplayLink来检测FPS,CADisplayLink则是使用帧率来作为时间间隔的单位

- (void)setupDisplayLink {

   //创建CADisplayLink,并添加到当前run loop的NSRunLoopCommonModes
   _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTicks:)];
   [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)linkTicks:(CADisplayLink *)link {

   //执行次数
   _scheduleTimes ++;

   //当前时间戳
   if(_timestamp == 0){
       _timestamp = link.timestamp;
   }
   
   CFTimeInterval timePassed = link.timestamp - _timestamp;

   if(timePassed >= 1.f)
       //fps
       CGFloat fps = _scheduleTimes/timePassed; 
       printf("fps:%.1f, timePassed:%f\n", fps, timePassed);

       //reset
       _timestamp = link.timestamp;
       _scheduleTimes = 0;
   }
} 
复制代码

我们也可以通过Xcode中的Instruments中的Core Animation来监测FPS

在上述FPS指示器中,如果将CADisplayLink放置于子线程的Runloop中,将会发生什么?

答案是无论主线程有多么繁忙,GPU占用有多么高,FPS始终是60,原因是基于CADisplayLink的FPS指示器只能检测到当前RunLoop的FPS

Runloop如何检测卡顿

由于runloop会调起同步屏幕刷新的callback,如果loop的间隔大于16.67ms,fps自然达不到60hz。而在一个loop当中存在多个阶段,可以监控每一个阶段停留了多长时间

- (void)startRunloopMonitorFreeze {
    
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
       
        
       ///如果CFAbsoluteTimeGetCurrent() - 上一次的时间差 比阈值大,说明卡顿了,APP处于闲置状态常驻beforeWaiting状态,判断不准确
        
        
    });
    
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    
}
复制代码

这种检测方式当APP处于闲置状态Runloop常驻beforeWaiting状态,判断不准确

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享