美文网首页
【iOS性能监控】- FPS

【iOS性能监控】- FPS

作者: Shawn_Y | 来源:发表于2022-03-16 12:04 被阅读0次

    本篇文章主要包含以下方面
    1、如何获取FPS数据
    2、如何处理数据
    3、上传策略

    如何获取FPS数据

    创建CADisplayLink,设置CADisplayLink的target的时候,用中间代理类做一次转发,代理类内部弱引用当前self,防止产生循环强引用。设置每秒的刷新帧率为60次,由于高刷屏的出现,这里可能需要做些微调。

    - (CADisplayLink *)displayLink
    {
        // Lazily create the display link.
        if (_displayLink == nil)
        {
            _displayLink = [CADisplayLink displayLinkWithTarget:[MMAPMWeakProxy proxyWithTarget:self] selector:@selector(updateFPSAction:)];
            _displayLink.preferredFramesPerSecond = 60;
        }
        return _displayLink;
    }
    

    添加到子线程RunLoop中,需要注意的是子线程要做保活,这里不再赘述具体怎么做

    - (void)start
    {
        if (_displayLink != nil)
        {
            return;
        }
        [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    

    displayLink的回调,更新计数count,超过1秒采样一次,计算间隔时间内的刷新频率,fps就是我们需要的数据

    - (void)updateFPSAction:(CADisplayLink *)displayLink
    {
        if (self.lastUpdateTime == 0)
        {
            self.lastUpdateTime = displayLink.timestamp;
        }
        ++self.count;
        NSTimeInterval interval = displayLink.timestamp - self.lastUpdateTime;
        if (interval < self.updateFPSInterval)
        {
            return;
        }
        self.lastUpdateTime = displayLink.timestamp;
        self.fps = self.count/interval;
        self.count = 0;
    }
    

    如何处理数据

    首先思考一个问题,做FPS监控的目的是什么?

    1. 获得app的FPS指标
    2. 优化低刷新率的页面,获得更好的用户体验

    所以FPS需要按用户进入的页面进行分组,在进入页面的时候调用

    - (void)setVCPageName:(NSString *)vcName pageType:(NSString *)pageType
    {
        @synchronized (self) {
            self.vcName = [MMAPMUtil shouldRecordForVCPageName:vcName] == YES ? vcName : nil;
        }
    }
    

    创建GCDTimer,每秒获取一次fps数据。

    • 如果fps帧率小于设定的阈值立马上报,这里的阈值默认是50,可以配制成服务端获取变量
    • 如果没有小于最小fps值,则每隔 sysLogTriggerInterval 时间取平均数上报,这里设置为1分钟
    - (void)addTimer
    {
        [self removeTimer];
        self.GCDTimer = [[AndyGCDTimer alloc] initInQueue:[AndyGCDQueue globalQueue]];
        __weak typeof(self) weakSelf = self;
        [self.GCDTimer timerExecute:^{
            __strong typeof(weakSelf) strongSelf = weakSelf;
            if (strongSelf.vcName.length == 0) return;
            
            NSUInteger fps = [strongSelf.fpsMonitor getFPS];
            // 如果 当前fps 小于 最小设定值,则立刻触发上报
            if (fps <= [MMAPMConfig sharedConfig].FPSPerfMinValue)
            {
                // 触发上报就拿当前的 fps记录到 log v2
                @synchronized (strongSelf) {
                    // 首先从缓存中移除记录
                    if ( !MM_IS_STR_NIL(strongSelf.vcName) ) {
                        [strongSelf.pageSysDictM removeObjectForKey:strongSelf.vcName];
                    }
                }
                // 立刻上报当前页面的sys信息
                [strongSelf recordVCName:strongSelf.vcName fps:fps];
            }
            else
            {
                // 不断组合页面sys信息
                [strongSelf combineSysFPS:fps];
                
                // 如果没有小于最小fps值,则每隔 sysLogTriggerInterval 时间取平均数上报
                NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
                if (strongSelf->_lastLogTimestamp + 60 < now)
                {
                    // 组合fps信息写入log v2 日志系统
                    [strongSelf recordLog];
                }
            }
            
        } timeIntervalWithSecs:1];
        
        [self.GCDTimer resume];
    }
    

    按页面分组,把fps数据添加到数组中

    - (void)combineSysFPS:(NSUInteger)fps
    {
        @synchronized (self) {
            if (self.vcName.length == 0) return;
            
            NSMutableDictionary *recordDictM = self.pageSysDictM[self.vcName];
            if (recordDictM == nil)
            {
                recordDictM = [NSMutableDictionary dictionary];
            }
            
            NSMutableArray *fpsArrM = recordDictM[FPS_KEY];
            if (fpsArrM == nil)
            {
                fpsArrM = [NSMutableArray array];
                recordDictM[FPS_KEY] = fpsArrM;
            }
            [fpsArrM addObject:@(fps)];
          
            [self.pageSysDictM setValue:recordDictM forKey:self.vcName];
        }
    }
    

    上传策略

    计算每个页面的fps平均值

    - (void)recordLog
    {
        // 如果当前触发日志记录,则及时记录更新当前log的时间
        NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
        _lastLogTimestamp = now;
        
        @synchronized (self) {
            [self.pageSysDictM enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull vcName_Key, NSMutableDictionary * _Nonnull recordDict, BOOL * _Nonnull stop) {
                __block NSUInteger fps_avg;
                [recordDict enumerateKeysAndObjectsUsingBlock:^(NSString *  _Nonnull key, NSArray *  _Nonnull arr, BOOL * _Nonnull stop) {
                    if ([key isEqualToString:FPS_KEY])
                    {
                        fps_avg = @(round(1.0 * [[arr valueForKeyPath:@"@sum.unsignedIntegerValue"] unsignedIntegerValue] / arr.count)).unsignedIntegerValue;
                    }
                }];
                
                [self recordVCName:vcName_Key fps:fps_avg ];
            }];
            
            [self.pageSysDictM removeAllObjects];
        }
    }
    

    上传到服务器

    - (void)recordVCName:(NSString *)vcName fps:(NSUInteger)fps
    {
        NSString *pageType = nil;
        @synchronized (self) {
            pageType = self.vc2PageTypeDictM[vcName];
        }
        if (vcName.length == 0 || pageType.length == 0 || [vcName hasPrefix:@"/"]) return;
        NSString *fpsStr = @(fps).stringValue;
     
     
        //记录到log v2 日志库
        NSDictionary *dict = @{@"fps" : fpsStr};
        [MMAPMLogger trackEvent:Track_Event_FPS_Monitor eventParams:@{@"page_name" : mm_safes(vcName), @"page_type":mm_safes(pageType), @"sys_data" : dict}];
    }
    

    相关文章

      网友评论

          本文标题:【iOS性能监控】- FPS

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