「若是某個實體表現出如下任何一種特性,它就具有自主性:自我修復、自我保護、自我維護、對目標的自我控制、自我改進。」 —— 凱文·凱利html
iOS App 有時可能遇到啓動必 crash 的絕境:每次打開 App 都閃退,沒法正常使用App。git
爲了嘗試解決這個問題,微信讀書開發了 iOS 連續閃退保護工具:GYBootingProtection,檢測連續閃退,在連續閃退出現時,嘗試自修復 App:github
本文探討了連續閃退問題的產生緣由、檢測、修復機制,以及如何在你的項目中引入、測試和使用GYBootingProtection。sql
首先要檢測用戶 App 出現了連續閃退的狀況,有兩種檢測方法,捕獲異常和計時器。數據庫
檢測連續閃退,能夠經過捕獲異常來實現,異常有如下種類:編程
在念茜的漫談 iOS Crash 收集框架一文中詳細介紹了 Mach 異常和 Unix 信號捕獲 crash 的機制。簡單來講,異常通常產生自 iOS 的微內核 Mach,而後在 BSD 層轉換成 UNIX SIGABRT 信號,以標準 POSIX 信號的形式提供給用戶。NSException 是使用者在處理 App 邏輯時,用編程的方法拋出。數組
經過如下方法捕獲異常:微信
Crash 上報工具如 PLCrashReporter 經過註冊 Mach 異常 + UNIX信號 的 handler 達到檢測的目的,對用戶提供了處理異常的接口。網絡
能夠利用 PLCrashReporter 這類工具來檢測連續閃退:app
經過 Mach 異常、Unix 信號、NSException 異常來檢測閃退,能得到更多的 crash 上下文,但因爲 crash 收集框架多使用這些方法,可能會有這樣的風險:與第三方 crash 收集框架衝突致使漏檢測。另外,可能會與 App 已有的異常處理代碼產生耦合。
除了經過捕獲異常的方式檢測連續閃退,還能夠經過計數器方法來檢測:
而計數器方法邏輯簡單,與原有的代碼耦合小。雖然有誤報可能(在啓動後當即被 kill 掉,誤認爲 crash),可是能夠經過設置閾值來減少誤報的誤報率。
綜上權衡,咱們使用計時器方法檢測連續閃退。
檢測到連續閃退後,接下來要嘗試對閃退進行修復,這裏先分析可能的閃退緣由,再結合微信讀書的例子說明修復流程。
連續閃退,多是 App 啓動關鍵路徑中執行了必 crash 的代碼,緣由可能有:
@try...catch
,損壞文件會拋出 NSException
致使 crash-objectAtIndex
方法會產生 crash: unknow selector send to object;
,或返回破損的 Tar 包,在解壓失敗致使 crash。針對 1,能夠經過工具修復數據庫,或者刪除 DB。針對2,能夠刪除文件來進行修復。對於 3 和 4,咱們須要具體地分析 crash 案例,經過 JSPatch 來進行修復。
爲了應對上述致使連續閃退的緣由,微信讀書的修復流程爲:
嘗試下載並執行 JSPatch 補丁
這裏是爲了解決上述第4點 - 代碼 bug 致使的閃退,使用 JSPatch [github]能夠進行熱修復。在 didFinishLaunching 時,會卡住界面發請求檢查是否有可用的 JSPatch 腳本,若是有則加載執行,解決代碼 bug 致使的閃退。
嘗試刪除 Documents
/ Library
/ Caches
目錄下的全部文件
這裏直接刪除了全部用戶數據,適用於微信讀書這種全部數據都在雲端,刪除後能夠徹底從雲端恢復。若是你的 App 不屬於這種場景,那麼應該在 repairBlock 中自定義修復邏輯,好比:
a. 不刪除文件,只修復數據庫
b. 修復前把用戶數據備份到雲端
c. 收集 crash 樣本,查明緣由,定製 JSPatch 修復補丁並下發
退出微信讀書登陸狀態
連續閃退檢測 + 保護流程如圖所示:
檢測和連續 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 重置初始狀態,連續閃退問題解決:
https://github.com/liuslevis/GYBootingProtection
原博客地址:http://wereadteam.github.io/2016/05/23/GYBootingProtection/#rd