【iOS性能监控】- FPS
2022-03-16 本文已影响0人
Shawn_Y
本篇文章主要包含以下方面
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监控的目的是什么?
- 获得app的FPS指标
- 优化低刷新率的页面,获得更好的用户体验
所以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}];
}