iOS App卡頓監控(Freezing/Lag)
筆記(寫在前面):
關於應用的效能監控,需要從多方面進行綜合考慮,此處僅從其中一個方面,進行學習研究。
如何判斷主執行緒卡頓:
監測NSRunLoop耗時情況。
NSRunLoop的呼叫主要在
kCFRunLoopBeforeSources
和kCFRunLoopBeforeWaiting
之間,以及kCFRunLoopAfterWaiting
之後。因此,若是發現這個兩個時間內耗時過長,就可以判定此時主執行緒出現卡頓情況。
一、監控NSRunLoop狀態變化
使用CFRunLoopObserverRef,實時獲得這些狀態值的變化,如下:
/// RunLoop狀態觀察回撥
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
<#MyClass#> *object = (__bridge <#MyClass#>*)info;
// 記錄狀態值
object->activity = activity;
}
/// 註冊RunLoop狀態觀察
- (void)registerRunLoopObserver {
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}
二、RunLoop耗時計算
另外開啟一個執行緒,實時計算兩個狀態區域之間的耗時,是否達到閾值。
dispatch_semaphore_t
讓子執行緒更及時地獲知主執行緒NSRunLoop狀態變化卡頓覆蓋範圍:
多次連續小卡頓
、單次長時間卡頓
新增計算邏輯,如下:
/// RunLoop狀態觀察回撥
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
<#MyClass#> *object = (__bridge <#MyClass#>*)info;
// 記錄狀態值
object->activity = activity;
// 傳送訊號
dispatch_semaphore_t semaphore = object->semaphore;
dispatch_semaphore_signal(semaphore);
}
/// 註冊RunLoop狀態觀察,並計算是否卡頓
- (void) registerRunLoopObserver {
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 建立訊號
semaphore = dispatch_semaphore_create(0);
// 在子執行緒監控時長
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (YES) {
// 假定連續5次超時50ms認為卡頓(當然也包含了單次超時250ms)
long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
if (st != 0) {
if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting) {
if (++timeoutCount < 5) {
continue;
}
// 發現卡頓
NSLog(@"卡、卡、卡、頓、頓、了");
}
}
timeoutCount = 0;
}
});
}
三、記錄卡頓的函式呼叫
目擊卡頓現場,記錄此時的呼叫函式資訊,作為卡頓證據。
此處,使用第三方Crash收集元件PLCrashReporter
,它不僅可以收集Crash資訊,也可用於實時獲取各執行緒的呼叫堆疊,使用示例如下:
PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD
symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];
PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];
NSData *data = [crashReporter generateLiveReport];
PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];
NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter
withTextFormat:PLCrashReportTextFormatiOS];
NSLog(@"------------
%@
------------", report);
特別注意:
PLCrashReporter
雖然能提供較為準確的堆疊資訊,用於定位問題,特別是使用符號化策略PLCrashReporterSymbolicationStrategyAll
時,能夠對堆疊資訊進行符號化,但會消耗大量資源,需要佔用較多時間,導致卡死現象(自測時,耗時超過7s,層多次到10s以上)。不使用符號化策略
PLCrashReporterSymbolicationStrategyNone
,測試時,平均耗時也接近3s。因此,加入該資訊採集,需要特別注意,建議僅在開發除錯階段使用。
為了投入線上使用,還需要再想想如何解決該問題。
四、上報伺服器
檢測到卡頓,獲取到呼叫堆疊資訊,客戶端再根據實際情況進行一定程度的過濾處理,將有價值的資訊上報伺服器。
後續對伺服器收集到的資料進行分析,定位需要優化的功能邏輯。