iOS 啓動連續閃退保護方案

版權聲明:本文由劉笑江原創文章,轉載請註明出處: 
文章原文連接:https://www.qcloud.com/community/article/79html

來源:騰雲閣 https://www.qcloud.com/communitygit

 

一.引言

「若是某個實體表現出如下任何一種特性,它就具有自主性:自我修復、自我保護、自我維護、對目標的自我控制、自我改進。」 —— 凱文·凱利github

iOS App 有時可能遇到啓動必 crash 的絕境:每次打開 App 都閃退,沒法正常使用App。sql

爲了嘗試解決這個問題,微信讀書開發了 iOS 連續閃退保護工具:GYBootingProtection,檢測連續閃退,在連續閃退出現時,嘗試自修復 App:數據庫

本文探討了連續閃退問題的產生緣由、檢測、修復機制,以及如何在你的項目中引入、測試和使用 GYBootingProtection編程

二.連續閃退檢測

首先要檢測用戶 App 出現了連續閃退的狀況,有兩種檢測方法,捕獲異常和計時器。數組

1.捕獲異常

檢測連續閃退,能夠經過捕獲異常來實現,異常有如下種類:微信

  • Mach 異常:EXC_CRASH
  • UNIX 信號:SIGABRT
  • NSException 異常:應用層,經過 NSUncaughtExceptionHandler 捕獲

在念茜的漫談 iOS Crash 收集框架一文中詳細介紹了 Mach 異常和 Unix 信號捕獲 crash 的機制。簡單來講,異常通常產生自 iOS 的微內核 Mach,而後在 BSD 層轉換成 UNIX SIGABRT 信號,以標準 POSIX 信號的形式提供給用戶。NSException 是使用者在處理 App 邏輯時,用編程的方法拋出。網絡

如何捕獲異常

經過如下方法捕獲異常:app

  • 利用 Mach API 捕獲 Mach 異常
  • 經過 POSIX API 註冊 signal(SIGSEGV,signalHandler) 來捕獲 UNIX 異常信號
  • 註冊 NSUncaughtExceptionHandler 來捕獲應用級異常

Crash 上報工具如 PLCrashReporter 經過註冊 Mach 異常 + UNIX信號 的 handler 達到檢測的目的,對用戶提供了處理異常的接口。

如何檢測

能夠利用 PLCrashReporter 這類工具來檢測連續閃退:

  1. 首先維護一個計數變量,表示連續閃退次數
  2. 在 PLCrashReporter 的 crash handler 中加入邏輯:若是啓動 5s 內 crash 使計數器加一
  3. 每次啓動時,若是連續閃退計數 > n,則檢測到了連續閃退
  4. 啓動後,執行一個定時任務,在 5s 後重置計數(若是 App 連續閃退則不會重置)

流程圖

優缺點

經過 Mach 異常、Unix 信號、NSException 異常來檢測閃退,能得到更多的 crash 上下文,但因爲 crash 收集框架多使用這些方法,可能會有這樣的風險:與第三方 crash 收集框架衝突致使漏檢測。另外,可能會與 App 已有的異常處理代碼產生耦合。

2.計時器方法

除了經過捕獲異常的方式檢測連續閃退,還能夠經過計數器方法來檢測:

  1. 維護一個計數變量,用於表示連續閃退的次數
  2. 在啓動 application:didFinishLaunchingWithOptions: 後使計數加一
  3. 接着使用 dispatch_after 方法在 5s 後清零計數,若是 App 活不過 5 秒計數就不會被清零
  4. 若是發現計數變量 > n,代表 App 連續 n 次連續閃退,啓動保護流程,重置計數。
  5. 當保護流程完成後,進入 App 正常啓動流程

流程圖

優缺點

而計數器方法邏輯簡單,與原有的代碼耦合小。雖然有誤報可能(在啓動後當即被 kill 掉,誤認爲 crash),可是能夠經過設置閾值來減少誤報的誤報率。

綜上權衡,咱們使用計時器方法檢測連續閃退。

三.連續閃退修復

檢測到連續閃退後,接下來要嘗試對閃退進行修復,這裏先分析可能的閃退緣由,再結合微信讀書的例子說明修復流程。

1.閃退緣由

連續閃退,多是 App 啓動關鍵路徑中執行了必 crash 的代碼,緣由可能有:

  1. 數據庫損壞:在平常使用如異常退出、斷電,或者錯誤的操做(參考:sqlite corruption causes)。
  2. 文件損壞:處理文件時若是沒有 @try...catch,損壞文件會拋出 NSException 致使 crash
  3. 網絡返回數據處理異常:好比預期返回數組,但實際返回了字典,對字典對象執行 -objectAtIndex 方法會產生 crash: unknow selector send to object;,或返回破損的 Tar 包,在解壓失敗致使 crash。
  4. 代碼 bug:當必 crash 的代碼出如今啓動關鍵路徑中,就會致使連續閃退。
  5. 針對 1,能夠經過工具修復數據庫,或者刪除 DB。針對2,能夠刪除文件來進行修復。對於 3 和 4,咱們須要具體地分析 crash 案例,經過 JSPatch 來進行修復。

2.微信讀書的修復流程

爲了應對上述致使連續閃退的緣由,微信讀書的修復流程爲:

  1. 進入 didFinishLaunch 時檢查是否有連續閃退,無則執行 5
  2. 彈 Toast 提示用戶是否修復,輕觸『修復』執行2,不然執行 5
  3. 嘗試下載並執行 JSPatch 補丁

    這裏是爲了解決上述第4點 - 代碼 bug 致使的閃退,使用 JSPatch [github]能夠進行熱修復。在 didFinishLaunching 時,會卡住界面發請求檢查是否有可用的 JSPatch 腳本,若是有則加載執行,解決代碼 bug 致使的閃退。

  4. 嘗試刪除Documents /Library / Caches 目錄下的全部文件

  5. 這裏直接刪除了全部用戶數據,適用於微信讀書這種全部數據都在雲端,刪除後能夠徹底從雲端恢復。若是你的 App 不屬於這種場景,那麼應該在 repairBlock 中自定義修復邏輯,好比:

    a. 不刪除文件,只修復數據庫
    b. 修復前把用戶數據備份到雲端
    c. 收集 crash 樣本,查明緣由,定製 JSPatch 修復補丁並下發

  6. 退出微信讀書登陸狀態

  7. 進入原 didFinishLaunch
    連續閃退檢測 + 保護流程如圖所示:

3.實現

檢測和連續 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 便可。

4.使用

引入項目

  1. 下載 (github) 源碼 ,將 src 目錄下全部文件拖拽到你的 Xcode 項目

  2. 在 AppDelegate+GYBootingProtection.m 的 onBeforeBootingProtection方法中添加檢測前須要執行的代碼,好比設置crash上報:

    (void)onBeforeBootingProtection {
    [GYBootingProtection setLogger:^(NSString *msg) {
      // setup logger
      NSLog(@"%@", msg);
    }];
    
    [GYBootingProtection setReportBlock:^(NSInteger crashCounts) {
      // setup crash report
    }];
    }
  3. 在 onBootingProtection 方法中添加修復邏輯,好比刪除文件:

    (void)onBootingProtection {
    // 檢查 JSPatch 更新
    ...
    // 刪除 Documents Library Caches 目錄下全部文件
    [GYBootingProtection deleteAllFilesUnderDocumentsLibraryCaches];
       ...
    }

    如需執行異步的修復邏輯,在 onBootingProtectionWithCompletion: 方法添加修復邏輯,並在完成修復後調用 completion :

    (void)onBootingProtectionWithCompletion:(BoolCompletionBlock)completion {
          [self onBootingProtection];
       // 異步修復
          [self asyncRepairWithCompletion:^(void) {
        // 正常啓動流程
              if (completion) completion();
          }];
    }

5.測試

  1. 首先製造連續閃退場景:

    啓動後 5 秒內,雙擊 Home 經過上劃手勢 kill 掉 App,重複屢次。(也能夠在代碼里人爲製造crash)

  2. 當連續閃退超過 5 次時,會提示用戶修復:

  3. 用戶輕觸修復,App 重置初始狀態,連續閃退問題解決:

源碼
https://github.com/liuslevis/GYBootingProtection

相關文章
相關標籤/搜索