版權聲明:本文由劉笑江原創文章,轉載請註明出處:
文章原文連接:https://www.qcloud.com/community/article/79html
來源:騰雲閣 https://www.qcloud.com/communitygit
「若是某個實體表現出如下任何一種特性,它就具有自主性:自我修復、自我保護、自我維護、對目標的自我控制、自我改進。」 —— 凱文·凱利github
iOS App 有時可能遇到啓動必 crash 的絕境:每次打開 App 都閃退,沒法正常使用App。sql
爲了嘗試解決這個問題,微信讀書開發了 iOS 連續閃退保護工具:GYBootingProtection,檢測連續閃退,在連續閃退出現時,嘗試自修復 App:數據庫
本文探討了連續閃退問題的產生緣由、檢測、修復機制,以及如何在你的項目中引入、測試和使用 GYBootingProtection。編程
首先要檢測用戶 App 出現了連續閃退的狀況,有兩種檢測方法,捕獲異常和計時器。數組
檢測連續閃退,能夠經過捕獲異常來實現,異常有如下種類:微信
在念茜的漫談 iOS Crash 收集框架一文中詳細介紹了 Mach 異常和 Unix 信號捕獲 crash 的機制。簡單來講,異常通常產生自 iOS 的微內核 Mach,而後在 BSD 層轉換成 UNIX SIGABRT 信號,以標準 POSIX 信號的形式提供給用戶。NSException 是使用者在處理 App 邏輯時,用編程的方法拋出。網絡
經過如下方法捕獲異常:app
Crash 上報工具如 PLCrashReporter 經過註冊 Mach 異常 + UNIX信號 的 handler 達到檢測的目的,對用戶提供了處理異常的接口。
能夠利用 PLCrashReporter 這類工具來檢測連續閃退:
經過 Mach 異常、Unix 信號、NSException 異常來檢測閃退,能得到更多的 crash 上下文,但因爲 crash 收集框架多使用這些方法,可能會有這樣的風險:與第三方 crash 收集框架衝突致使漏檢測。另外,可能會與 App 已有的異常處理代碼產生耦合。
除了經過捕獲異常的方式檢測連續閃退,還能夠經過計數器方法來檢測:
而計數器方法邏輯簡單,與原有的代碼耦合小。雖然有誤報可能(在啓動後當即被 kill 掉,誤認爲 crash),可是能夠經過設置閾值來減少誤報的誤報率。
綜上權衡,咱們使用計時器方法檢測連續閃退。
檢測到連續閃退後,接下來要嘗試對閃退進行修復,這裏先分析可能的閃退緣由,再結合微信讀書的例子說明修復流程。
連續閃退,多是 App 啓動關鍵路徑中執行了必 crash 的代碼,緣由可能有:
@try...catch
,損壞文件會拋出 NSException
致使 crash-objectAtIndex
方法會產生 crash: unknow selector send to object
;,或返回破損的 Tar 包,在解壓失敗致使 crash。爲了應對上述致使連續閃退的緣由,微信讀書的修復流程爲:
嘗試下載並執行 JSPatch 補丁
這裏是爲了解決上述第4點 - 代碼 bug 致使的閃退,使用 JSPatch [github]能夠進行熱修復。在 didFinishLaunching 時,會卡住界面發請求檢查是否有可用的 JSPatch 腳本,若是有則加載執行,解決代碼 bug 致使的閃退。
嘗試刪除Documents
/Library
/ Caches
目錄下的全部文件
這裏直接刪除了全部用戶數據,適用於微信讀書這種全部數據都在雲端,刪除後能夠徹底從雲端恢復。若是你的 App 不屬於這種場景,那麼應該在 repairBlock 中自定義修復邏輯,好比:
a. 不刪除文件,只修復數據庫
b. 修復前把用戶數據備份到雲端
c. 收集 crash 樣本,查明緣由,定製 JSPatch 修復補丁並下發
退出微信讀書登陸狀態
進入原 didFinishLaunch
連續閃退檢測 + 保護流程如圖所示:
檢測和連續 crash 並修復須要修改原-application:didFinishLaunchingWithOptions
: 邏輯,有幾種方法:
直接修改 -application:didFinishLaunchingWithOptions
: 方法。
新建一個 SubAppDelegate
類來繼承 AppDelegate
,覆蓋 -application:didFinishLaunchingWithOptions
: 方法,而後把 main()
函數中的AppDelegate
替換爲 SubAppDelegate
新建一個 AppDelegate
擴展,而後用 method swizzle
的方法替換 -application:didFinishLaunchingWithOptions
: 方法。
上述三種方案,對現有項目改動代價是 1 > 2 > 3。所以,咱們使用對源碼修改代價最小的方案 3 來替換-application:didFinishLaunchingWithOptions:
。
檢測的邏輯 GYBootingProtection 已經處理好,修復的處理預留了接口,能夠由用戶自定義,把自定義的修復流程傳入 repairBlock 便可。
引入項目
下載 (github) 源碼 ,將 src 目錄下全部文件拖拽到你的 Xcode 項目
在 AppDelegate+GYBootingProtection.m
的 onBeforeBootingProtection
方法中添加檢測前須要執行的代碼,好比設置crash上報:
(void)onBeforeBootingProtection { [GYBootingProtection setLogger:^(NSString *msg) { // setup logger NSLog(@"%@", msg); }]; [GYBootingProtection setReportBlock:^(NSInteger crashCounts) { // setup crash report }]; }
在 onBootingProtection 方法中添加修復邏輯,好比刪除文件:
(void)onBootingProtection { // 檢查 JSPatch 更新 ... // 刪除 Documents Library Caches 目錄下全部文件 [GYBootingProtection deleteAllFilesUnderDocumentsLibraryCaches]; ... }
如需執行異步的修復邏輯,在 onBootingProtectionWithCompletion
: 方法添加修復邏輯,並在完成修復後調用 completion :
(void)onBootingProtectionWithCompletion:(BoolCompletionBlock)completion { [self onBootingProtection]; // 異步修復 [self asyncRepairWithCompletion:^(void) { // 正常啓動流程 if (completion) completion(); }]; }
首先製造連續閃退場景:
啓動後 5 秒內,雙擊 Home 經過上劃手勢 kill 掉 App,重複屢次。(也能夠在代碼里人爲製造crash)
當連續閃退超過 5 次時,會提示用戶修復:
用戶輕觸修復,App 重置初始狀態,連續閃退問題解決: