对应着本文的三大模块:监控,工具和最佳实践。
监控
==
启动埋点
既然要监控,那么就要能够在代码里获取到启动时长。启动的起点大家采用的方案都一样:进程创建的时间。
启动的终点对应用户感知到的 Launch Image 消失的第一帧,抖音采用的方案如下:
-
iOS 12 及以下:root viewController 的 viewDidAppear
-
iOS 13+:applicationDidBecomeActive
Apple 官方的统计方式是第一个 CA::Transaction::commit
,但对应的实现在系统框架内部,抖音的方式已经非常接近这个点了。
分阶段
只有一个启动耗时的埋点在排查线上问题的时候显然是不够的,可以通过分阶段和单点埋****点结合,下面是这是目前抖音的监控方案:
+load、initializer 的调用顺序和链接顺序有关,链接顺序默认按照 CocoaPod 的 Pod 命名升序排列,所以取一个命名为 AAA 开头既可以让某个 +load、initializer 第一个被执行。
无侵入监控
公司的 APM 团队提供了一种无侵入的启动监控方案,方案将启动流程拆分成几个粒度比较粗的与业务无关的阶段:进程创建,最早的 +load,didFinishLuanching 开始和首屏首次绘制完成。
前三个时间点无侵入获取较为简单
-
进程创建:通过
sysctl
系统调用拿到进程创建的时间戳 -
最早的 +load:和上面的分阶段监控一样,通过 AAA 为前缀命名 Pod,让 +load 第一个被执行
-
didFinishLaunching:监控 SDK 初始化一般在启动的很早期,用监控 SDK 的初始化时间作为 didFinishLaunching 的时间
首屏渲染完成时间我们希望和 MetricKit
对齐,即获取到 CA::Transaction::commit()
方法被调用的时间。
通过 Runloop 源码分析和线下调试,我们发现 CA::Transaction::commit()
,CFRunLoopPerformBlock
,kCFRunLoopBeforeTimers
这三个时机的顺序从早到晚依次是:
可以通过在 didFinishLaunch 中向 Runloop 注册 block 或者 BeforeTimer 的 Observer 来获取上图中两个时间点的回调,代码如下:
//注册block
CFRunLoopRef mainRunloop = [[NSRunLoop mainRunLoop] getCFRunLoop];
CFRunLoopPerformBlock(mainRunloop,NSDefaultRunLoopMode,^(){
NSTimeInterval stamp = [[NSDate date] timeIntervalSince1970];
NSLog(@“runloop block launch end:%f”,stamp);
});
//注册kCFRunLoopBeforeTimers回调
CFRunLoopRef mainRunloop = [[NSRunLoop mainRunLoop] getCFRunLoop];
CFRunLoopActivity activities = kCFRunLoopAllActivities;
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, activities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if (activity == kCFRunLoopBeforeTimers) {
NSTimeInterval stamp = [[NSDate date] timeIntervalSince1970];
NSLog(@“runloop beforetimers launch end:%f”,stamp);
CFRunLoopRemoveObserver(mainRunloop, observer, kCFRunLoopCommonModes);
}
});
CFRunLoopAddObserver(mainRunloop, observer, kCFRunLoopCommonModes);
经过实测,我们最后选择的无侵入获取首屏渲染方案是:
-
iOS13(含)以上的系统采用
runloop
中注册一个kCFRunLoopBeforeTimers
的回调获取到的 App 首屏渲染完成的时机更准确。 -
iOS13 以下的系统采用
CFRunLoopPerformBlock
方法注入 block 获取到的 App 首屏渲染完成的时机更准确。
监控周期
App 的生命周期可以分为三个阶段:研发,灰度和线上,不同阶段监控的目的和方式都不一样。
研发阶段
研发阶段的监控主要目的是防止劣化,对应着会有线下的自动化监控,通过实际的启动性能测试来尽早地发现和解决问题,抖音的线下自动化监控流程图如下:
由定时任务触发,先 release 模式下打包,接着跑一次自动化测试,测试完毕后会上报测试结果,方便通过看板来跟踪整体的变化趋势。
如果发现有劣化,会