iOS 利用 RunLoop 原理去監(jiān)控卡頓

本文是借鑒 戴銘老師 iOS開發(fā)高手課 內(nèi)容總結(jié)截驮。

目錄

1捌浩、卡頓問題

2啄糙、RunLoop介紹

3笛臣、RunLoop執(zhí)行過程 介紹

4、RunLoop全部六個狀態(tài)

5隧饼、RunLoop監(jiān)控卡頓操作?

6沈堡、直接用 PLCrashReporter這個開源的第三方庫來獲取堆棧信息

7、微信開源?matrix-ios卡頓監(jiān)控?工具

8燕雁、騰訊 Bugly 工具?Bugly?: 可監(jiān)控?App在運行過程中發(fā)生的 【崩潰诞丽、卡頓、ANR拐格、錯誤】

總結(jié)監(jiān)控卡頓Demo:Demo

1僧免、卡頓問題:卡頓問題,就是在主線程上無法響應(yīng)用戶交互的問題捏浊。如果一個 App 時不時地就給你卡一下懂衩,有時還長時間無響應(yīng)

1、卡頓根源:

? ? ? ? 1>復雜 UI 、圖文混排的繪制量過大浊洞;

? ? ? ? 2>在主線程上做網(wǎng)絡(luò)同步請求牵敷;

? ? ? ? 3>在主線程做大量的 IO 操作;

? ? ? ? 4>運算量過大法希,CPU 持續(xù)高占用枷餐;

? ? ? ? 5>死鎖和主子線程搶鎖。

2苫亦、FPS:FPS 是一秒顯示的幀數(shù)毛肋,也就是一秒內(nèi)畫面變化數(shù)量。如果按照動畫片來說著觉,動畫片的 FPS 就是 24,是達不到 60 滿幀的惊暴。也就是說饼丘,對于動畫片來說,24 幀時雖然沒有 60 幀時流暢辽话,但也已經(jīng)是連貫的了肄鸽,所以并不能說 24 幀時就算是卡住了。由此可見油啤,簡單地通過監(jiān)視 FPS 是很難確定是否會出現(xiàn)卡頓問題了典徘。

2、RunLoop介紹(推薦的監(jiān)控卡頓的方案是:通過監(jiān)控 RunLoop 的狀態(tài)來判斷是否會出現(xiàn)卡頓益咬。)

1逮诲、RunLoop原理:對于 iOS 開發(fā)來說,監(jiān)控卡頓就是要去找到主線程上都做了哪些事兒幽告。我們都知道梅鹦,線程的消息事件是依賴于 NSRunLoop 的,所以從 NSRunLoop 入手冗锁,就可以知道主線程上都調(diào)用了哪些方法齐唆。我們通過【監(jiān)聽 NSRunLoop 的狀態(tài),就能夠發(fā)現(xiàn)調(diào)用方法是否執(zhí)行時間過長】冻河,從而判斷出是否會出現(xiàn)卡頓箍邮。

2、RunLoop 是 iOS 開發(fā)中的一個基礎(chǔ)概念叨叙,它可以做哪些事兒锭弊,以及它為什么可以做成這些事兒?

RunLoop 這個對象擂错,在 iOS 里由 CFRunLoop 實現(xiàn)廷蓉。

【簡單來說,RunLoop 是用來監(jiān)聽輸入源,進行調(diào)度處理的】桃犬。這里的輸入源可以是輸入設(shè)備刹悴、網(wǎng)絡(luò)、周期性或者延遲時間攒暇、異步回調(diào)土匀。

RunLoop 會接收兩種類型的輸入源:一種是來自另一個線程或者來自不同應(yīng)用的異步消息;另一種是來自預訂時間或者重復間隔的同步事件形用。

【RunLoop 的目的是就轧,當有事件要去處理時保持線程忙,當沒有事件要處理時讓線程進入休眠田度《视】

所以,了解 RunLoop 原理不光能夠運用到監(jiān)控卡頓上镇饺,還可以提高用戶的交互體驗乎莉。通過將那些繁重而不緊急會大量占用 CPU 的任務(wù)(比如圖片加載),放到空閑的 RunLoop 模式里執(zhí)行奸笤,就可以避開在 UITrackingRunLoopMode 這個 RunLoop 模式時是執(zhí)行惋啃。UITrackingRunLoopMode 是用戶進行滾動操作時會切換到的 RunLoop 模式,避免在這個 RunLoop 模式執(zhí)行繁重的 CPU 任務(wù)监右,就能避免影響用戶交互操作上體驗边灭。

3、RunLoop執(zhí)行過程 介紹

1健盒、第一步通知 observers:RunLoop 要開始進入 loop 了绒瘦。緊接著就進入 loop

1

2、第二步開啟一個 do while 來笨垩ⅲ活線程椭坚。通知 Observers:RunLoop 會觸發(fā) Timer 回調(diào)、Source0 回調(diào)搏色,接著執(zhí)行加入的 block善茎。代碼如下:

2-0

接下來,觸發(fā) Source0 回調(diào)频轿,如果有 Source1 是 ready 狀態(tài)的話垂涯,就會跳轉(zhuǎn)到 handle_msg 去處理消息。代碼如下:

2-1

3航邢、第三步回調(diào)觸發(fā)后耕赘,通知 Observers:RunLoop 的線程將進入休眠(sleep)狀態(tài)。代碼如下:

3

4膳殷、第四步進入休眠后操骡,會等待 mach_port 的消息,以再次喚醒。只有在下面四個事件出現(xiàn)時才會被再次喚醒:

? ? 1>基于 port 的 Source 事件册招;

? ? ?2>Timer 時間到岔激;

? ? ?3>RunLoop 超時;

? ? ?4>被調(diào)用者喚醒是掰。

等待喚醒的代碼如下:

4

5虑鼎、第五步喚醒時通知 Observer:RunLoop 的線程剛剛被喚醒了。代碼如下:

5

6键痛、第六步RunLoop 被喚醒后就要開始處理消息了:

? ? ? ?1>如果是 Timer 時間到的話炫彩,就觸發(fā) Timer 的回調(diào);

? ? ? ?2>如果是 dispatch 的話絮短,就執(zhí)行 block江兢;

? ? ? ?3>如果是 source1 事件的話,就處理這個事件丁频。消息執(zhí)行完后杉允,就執(zhí)行加到 loop 里的 block。代碼如下:

6

7限府、第七步根據(jù)當前 RunLoop 的狀態(tài)來判斷是否需要走下一個 loop夺颤。當被外部強制停止或 loop 超時時痢缎,就不繼續(xù)下一個 loop 了胁勺,否則繼續(xù)走下一個 loop 。代碼如下:

7

整個 RunLoop 過程独旷,我們可以總結(jié)為如下所示的一張圖片署穗。RunLoop全部代碼過程

RunLoop全部流程


4、RunLoop全部六個狀態(tài)

loop 的六個狀態(tài)通過對 RunLoop 原理的分析嵌洼,我們可以看出在整個過程中案疲,loop 的狀態(tài)包括 6 個,其代碼定義如下:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {?

? ? ? ? ?kCFRunLoopEntry , // 進入 loop?

? ? ? ? ?kCFRunLoopBeforeTimers , // 觸發(fā) Timer 回調(diào)?

? ? ? ? ?kCFRunLoopBeforeSources , // 觸發(fā) Source0 回調(diào)?

? ? ? ? ?kCFRunLoopBeforeWaiting , // 等待 mach_port 消息?

? ? ? ? ?kCFRunLoopAfterWaiting , // 接收 mach_port 消息?

? ? ? ? ?kCFRunLoopExit , // 退出 loop?

? ? ? ? ?kCFRunLoopAllActivities // loop 所有狀態(tài)改變}

分析:如果 RunLoop 的線程麻养,【進入睡眠前方法的執(zhí)行時間過長而導致無法進入睡眠】褐啡,或者 【線程喚醒后接收消息時間過長而無法進入下一步的話】,就可以認為是 ——> 線程受阻了鳖昌”钙瑁【如果這個線程是主線程的話,表現(xiàn)出來的就是出現(xiàn)了卡頓】许昨。

如果我們要利用 RunLoop 原理來監(jiān)控卡頓的話懂盐,就是要關(guān)注這兩個階段。RunLoop 在進入睡眠之前和喚醒后的兩個 loop 狀態(tài)定義的值糕档,分別是 kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting 莉恼,也就是要觸發(fā) Source0 回調(diào)和接收 mach_port 消息兩個狀態(tài)。

5、RunLoop監(jiān)控卡頓操作 (參考資料)俐银、騰訊matirx 框架?matirx? 尿背、 或者?Gitee倉庫Matrix

1、開啟一個子線程監(jiān)控的代碼如下:代碼中的 NSEC_PER_SEC悉患,代表的是觸發(fā)卡頓的時間閾值残家,單位是秒∈墼辏可以看到坞淮,我們把這個閾值設(shè)置成了 3 秒。那么陪捷,這個 3 秒的閾值是從何而來呢回窘?這樣設(shè)置合理嗎?其實市袖,觸發(fā)卡頓的時間閾值啡直,我們可以根據(jù) WatchDog 機制來設(shè)置。WatchDog 在不同狀態(tài)下設(shè)置的不同時間苍碟,如下所示:啟動(Launch):20s酒觅;恢復(Resume):10s;掛起(Suspend):10s微峰;退出(Quit):6s舷丹;后臺(Background):3min(在 iOS 7 之前,每次申請 10min蜓肆; 之后改為每次申請 3min颜凯,可連續(xù)申請,最多申請到 10min)仗扬。通過 WatchDog 設(shè)置的時間症概,我認為可以把啟動的閾值設(shè)置為 10 秒,其他狀態(tài)則都默認設(shè)置為 3 秒早芭”顺牵總的原則就是,要小于 WatchDog 的限制時間退个。當然了募壕,這個閾值也不用小得太多,原則就是要優(yōu)先解決用戶感知最明顯的體驗問題帜乞。

2司抱、如何獲取卡頓的方法堆棧信息?

子線程監(jiān)控發(fā)現(xiàn)卡頓后黎烈,還需要記錄當前出現(xiàn)卡頓的方法堆棧信息习柠,并適時推送到服務(wù)端供開發(fā)者分析匀谣,從而解決卡頓問題。那么资溃,在這個過程中武翎,如何獲取卡頓的方法堆棧信息呢?

獲取堆棧信息的一種方法是直接調(diào)用系統(tǒng)函數(shù)溶锭。

這種方法的優(yōu)點在于宝恶,性能消耗小。但是趴捅,它只能夠獲取簡單的信息垫毙,也沒有辦法配合 dSYM 來獲取具體是哪行代碼出了問題,而且能夠獲取的信息類型也有限拱绑。這種方法综芥,因為性能比較好,所以適用于觀察大盤統(tǒng)計卡頓情況猎拨,而不是想要找到卡頓原因的場景膀藐。

直接調(diào)用系統(tǒng)函數(shù)方法的主要思路是:用 signal 進行錯誤信息的獲取

6、直接用 PLCrashReporter這個開源的第三方庫來獲取堆棧信息

具體如何使用 PLCrashReporter 來獲取堆棧信息红省,代碼如下所示:

// 獲取數(shù)據(jù)

?NSData *lagData = [[[PLCrashReporter alloc] initWithConfiguration:[[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]] generateLiveReport];?

// 轉(zhuǎn)換成 PLCrashReport 對象?

PLCrashReport *lagReport = [[PLCrashReport alloc] initWithData:lagData error:NULL];?

// 進行字符串格式化處理?

NSString *lagReportString = [PLCrashReportTextFormatter stringValueForCrashReport:lagReport withTextFormat:PLCrashReportTextFormatiOS];

?//將字符串上傳服務(wù)器NSLog(@"lag happen, detail below: \n %@",lagReportString);

7额各、微信開源?matrix-ios卡頓監(jiān)測?https://github.com/tencent/matrix/tree/master/matrix/matrix-iOS? https://github.com/Tencent/matrix?工具

微信團隊就放出了一篇文章專門介紹卡頓監(jiān)控方案“微信 iOS 卡頓監(jiān)控系統(tǒng)”鏈接。之后吧恃,很多團隊參照這篇文章開發(fā)了自己的卡頓監(jiān)控系統(tǒng)虾啦。

1> 今年的 4 月 3 號,微信團隊將他們的卡頓監(jiān)控系統(tǒng)matrix開源出來了蚜枢,包括 Matrix for iOS / MacOS https://github.com/Tencent/matrix/tree/master/matrix/matrix-iOS?和Android系統(tǒng)的監(jiān)控方案缸逃。關(guān)于 matrix-iOS 的卡頓監(jiān)控原理针饥,你可以點擊這個鏈接?https://github.com/Tencent/matrix/wiki/Matrix-for-iOS-macOS-卡頓監(jiān)控原理?查看厂抽。

如果你的 App 現(xiàn)在還沒有卡頓監(jiān)控系統(tǒng),可以考慮直接集成 matrix-iOS丁眼,直接在 Podfile 里添加 pod ‘matrix-wechat’ 就可以了筷凤。如果已經(jīng)有了卡頓監(jiān)控系統(tǒng),我建議你閱讀下 matrix-iOS 的代碼苞七,里面有很多細節(jié)值得我們學習藐守。

比如:子線程監(jiān)控檢測時間間隔:matrix-iOS 監(jiān)控卡頓的子線程是通過 NSThread 創(chuàng)建的,檢測時間間隔正常情況是 1 秒蹂风,在出現(xiàn)卡頓情況下卢厂,間隔時間會受檢測線程退火算法影響,按照斐波那契數(shù)列遞增惠啄,直到?jīng)]有卡頓時恢復為 1 秒慎恒。

2> 子線程監(jiān)控退火算法:避免一個卡頓會寫入多個文件的情況任内。

【為了降低檢測帶來的性能損耗,我們?yōu)闄z測線程增加了退火算法:

每次子線程檢查到主線程卡頓融柬,會先獲得主線程的堆棧并保存到內(nèi)存中(不會直接去獲得線程快照保存到文件中)死嗦;

將獲得的主線程堆棧與上次卡頓獲得的主線程堆棧進行比對:如果堆棧不同,則獲得當前的線程快照并寫入文件中粒氧;

如果相同則會跳過越除,并按照斐波那契數(shù)列將檢查時間遞增直到?jīng)]有遇到卡頓或者主線程卡頓堆棧不一樣。

這樣外盯,可以避免同一個卡頓寫入多個文件的情況摘盆;避免檢測線程遇到主線程卡死的情況下,不斷寫線程快照文件饱苟÷獬海】

3> RunLoop 卡頓時間閾值設(shè)置:對于 RunLoop 超時閾值的設(shè)置,微信設(shè)置的是 2 秒掷空。CPU 使用率閾值設(shè)置:當單核 CPU 使用率超過 80%肋殴,就判定 CPU 占用過高拗秘。CPU 使用率過高床嫌,可能導致 App 卡頓。

【Matrix 卡頓監(jiān)控在 Runloop 的起始 最開始和結(jié)束最末尾 ?位置添加 Observer怒详,從而獲得主線程的開始和結(jié)束狀態(tài)酿傍±优常卡頓監(jiān)控起一個子線程定時檢查主線程的狀態(tài),當主線程的狀態(tài)運行超過一定閾值則認為主線程卡頓赤炒,從而標記為一個卡頓氯析。目前微信使用的卡頓監(jiān)控,主程序 Runloop 超時的閾值是 2 秒莺褒,子線程的檢查周期是 1 秒掩缓。每隔 1 秒,子線程檢查主線程的運行狀態(tài)遵岩;如果檢查到主線程 Runloop 運行超過 2 秒則認為是卡頓你辣,并獲得當前的線程快照。同時尘执,我們也認為 CPU 過高也可能導致應(yīng)用出現(xiàn)卡頓舍哄,所以在子線程檢查主線程狀態(tài)的同時,如果檢測到 CPU 占用過高誊锭,會捕獲當前的線程快照保存到文件中表悬。目前微信應(yīng)用中認為,單核 CPU 的占用超過了 80%丧靡,此時的 CPU 占用就過高了蟆沫〔婕ィ】

4> 線程過多時 CPU 在切換線程上下文時,還會更新寄存器饥追,更新寄存器時需要尋址图仓,而尋址的過程還會有較大的 CPU 消耗。按照微信團隊的經(jīng)驗但绕,線程數(shù)超出 64 個時會導致主線程卡頓救崔,如果卡頓是由于線程多造成的,那么就沒必要通過獲取主線程堆棧去找卡頓原因了捏顺。根據(jù) matrix-iOS 的實測六孵,每隔 50 毫秒獲取主線程堆棧會增加 3% 的 CPU 占用,所以當檢測到主線程卡頓以后幅骄,我們需要先判斷是否是因為線程數(shù)過多導致的劫窒,而不是一有卡頓問題就去獲取主線程堆棧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拆座,一起剝皮案震驚了整個濱河市主巍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挪凑,老刑警劉巖孕索,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異躏碳,居然都是意外死亡搞旭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門菇绵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肄渗,“玉大人,你說我怎么就攤上這事咬最◆岬眨” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵丹诀,是天一觀的道長钝的。 經(jīng)常有香客問我翁垂,道長铆遭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任沿猜,我火速辦了婚禮枚荣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啼肩。我一直安慰自己橄妆,他們只是感情好衙伶,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著害碾,像睡著了一般矢劲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上慌随,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天芬沉,我揣著相機與錄音,去河邊找鬼阁猜。 笑死丸逸,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的剃袍。 我是一名探鬼主播黄刚,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼民效!你這毒婦竟也來了憔维?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤畏邢,失蹤者是張志新(化名)和其女友劉穎埋同,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棵红,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡凶赁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了逆甜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虱肄。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖交煞,靈堂內(nèi)的尸體忽然破棺而出咏窿,到底是詐尸還是另有隱情,我是刑警寧澤素征,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布集嵌,位于F島的核電站,受9級特大地震影響御毅,放射性物質(zhì)發(fā)生泄漏根欧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一端蛆、第九天 我趴在偏房一處隱蔽的房頂上張望凤粗。 院中可真熱鬧,春花似錦今豆、人聲如沸嫌拣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽异逐。三九已至捶索,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灰瞻,已是汗流浹背情组。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留箩祥,地道東北人院崇。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像袍祖,于是被迫代替她去往敵國和親底瓣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容