本篇文章主要包含以下方面
1、如何獲取FPS數(shù)據(jù)
2、如何處理數(shù)據(jù)
3、上傳策略
如何獲取FPS數(shù)據(jù)
創(chuàng)建CADisplayLink,設(shè)置CADisplayLink的target的時候,用中間代理類做一次轉(zhuǎn)發(fā),代理類內(nèi)部弱引用當前self,防止產(chǎn)生循環(huán)強引用。設(shè)置每秒的刷新幀率為60次,由于高刷屏的出現(xiàn),這里可能需要做些微調(diào)。
- (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的回調(diào),更新計數(shù)count,超過1秒采樣一次,計算間隔時間內(nèi)的刷新頻率,fps就是我們需要的數(shù)據(jù)
- (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;
}
如何處理數(shù)據(jù)
首先思考一個問題,做FPS監(jiān)控的目的是什么?
- 獲得app的FPS指標
- 優(yōu)化低刷新率的頁面,獲得更好的用戶體驗
所以FPS需要按用戶進入的頁面進行分組,在進入頁面的時候調(diào)用
- (void)setVCPageName:(NSString *)vcName pageType:(NSString *)pageType
{
@synchronized (self) {
self.vcName = [MMAPMUtil shouldRecordForVCPageName:vcName] == YES ? vcName : nil;
}
}
創(chuàng)建GCDTimer,每秒獲取一次fps數(shù)據(jù)。
- 如果fps幀率小于設(shè)定的閾值立馬上報,這里的閾值默認是50,可以配制成服務(wù)端獲取變量
- 如果沒有小于最小fps值,則每隔 sysLogTriggerInterval 時間取平均數(shù)上報,這里設(shè)置為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 小于 最小設(shè)定值,則立刻觸發(fā)上報
if (fps <= [MMAPMConfig sharedConfig].FPSPerfMinValue)
{
// 觸發(fā)上報就拿當前的 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 時間取平均數(shù)上報
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
if (strongSelf->_lastLogTimestamp + 60 < now)
{
// 組合fps信息寫入log v2 日志系統(tǒng)
[strongSelf recordLog];
}
}
} timeIntervalWithSecs:1];
[self.GCDTimer resume];
}
按頁面分組,把fps數(shù)據(jù)添加到數(shù)組中
- (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
{
// 如果當前觸發(fā)日志記錄,則及時記錄更新當前l(fā)og的時間
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];
}
}
上傳到服務(wù)器
- (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}];
}