iOS App 後臺任務的坑(奔潰緣由分析)

http://www.cocoachina.com/cms/wap.php?action=article&id=24864php

 

Background Task 花式 crash數據庫

Background Task 的 API 及其簡單,begin 和 end 之間的代碼所有進入 Background Task 的範疇。但簡單的代碼隱藏着不小的風險,下面列出三個比較容易出現的 crash。並且這三個 crash 都是客戶端自帶的 crash 採集工具沒法捕捉的,只能經過 Apple 的 crash 日誌得到信號。緣由很簡單,這些 crash 發生的時候 app 通常處於 suspend 狀態,根本沒有機會執行任何代碼,系統直接發送 SIGKILL 信號後就將 app 強殺,並生成一個系統日誌,一個只能 Apple 訪問的日誌,還得用戶先贊成上傳分享。安全

0xdead10cc微信

這個 crash 日誌通常長這樣:網絡

Exception Type:  EXC_CRASH (SIGKILL)session

Exception Codes: 0x0000000000000000, 0x0000000000000000app

Exception Note:  EXC_CORPSE_NOTIFY工具

Termination Reason: Namespace SPRINGBOARD, Code 0xdead10ccoop

Termination Description: SPRINGBOARD, com.xxx.xxx was task-suspended with locked system fileui

緣由我以前介紹過,當你的 App 有 Extension,並且 Extension 存在和 Host App 共享數據的需求,通常作法會將 db 文件放入 shared container 目錄下,此時你的 App 就有大機率會發生這種 crash。

App 進入後臺運行 Background Task,end 以後 App 被系統 suspend,若是 suspend 以後還存在任何訪問 db 的操做,此時 App 會立馬被系統強殺,這是 Apple 出於保護數據庫文件的完整的考慮。

因此正確的作法是將全部有可能在 App 進入後臺以後,還會發生的 db 操做通通封入 Background Task,以確保安全。這個代碼寫在 db layer 可能更加合適。

並且 Apple 推薦當你想啓動 Background Task 的時候,其實並不須要考慮當前 App 是出於 foreground 仍是 background,即便 App 在前臺啓動 Background Task,也並不會佔用進入後臺以後 3 分鐘額度,因此放心大膽的把關鍵代碼放進 Background Task 吧。

0xbada5e47

當你遵從了上面的建議,大大方方的把儘量多的關鍵代碼封入 Background Task 後,那麼你可能會遇到下面的 crash:

Exception Type:  EXC_CRASH (SIGKILL)

Exception Codes: 0x0000000000000000, 0x0000000000000000

Exception Note:  EXC_CORPSE_NOTIFY

Termination Reason: Namespace ASSERTIOND, Code 0xbada5e47

同理也是和 Background Task 相關,緣由是 Apple 認爲你啓動了過多的 Background Task,因此要殺掉。多少算多呢?幾十個很少,當前的 threshold 是 1000 個,超過 1000 個纔會強殺。若是你的 Background Task 封裝發生在 db layer,出現大量數據過來須要存儲或讀取的時候,仍是有可能會 hit 這個 limit。

另外一個 0xbada5e47 的可能緣由是,Background Task 在超時以後會調用 expiry handler,不管你有多少個 Background Task,全部 expiry handler 執行的時間不能超過若干秒,一旦超過也會被槍殺。因此在 expiry handler 裏面切忌有任何好比 disk io 的耗時操做。

0x8badf00d

說到 0x8badf00d,你們都很熟悉了,當你的主線程卡住的時間太長,系統的 Watchdog 會將你的 App 強殺,並生成一個帶有 0x8badf00d 的 crash 日誌。

Background Task 其實也能夠 0x8badf00d 的,好比:

Exception Type:  EXC_CRASH (SIGKILL)

Exception Codes: 0x0000000000000000, 0x0000000000000000

Exception Note:  EXC_CORPSE_NOTIFY

Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d

當你的代碼邏輯會產生 leaked Background Task 時,就會出現上面的系統強殺 crash 日誌了,什麼是 leaked Background Task 呢?看代

- (void)startBgTask
{
 self.bgTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
   NSLog(@"Expired: %lu", (unsigned long)self.bgTaskID);
   [[UIApplication sharedApplication] endBackgroundTask:self.bgTaskID];
 }];
}

- (void)endBgTask
{
 [[UIApplication sharedApplication] endBackgroundTask:self.bgTaskID];
}

 











上面的代碼若是 startBgTask 執行兩次,就必定會出現 leaked Background Task,由於 self.bgTaskID 第二次會被賦予一個新的 ID,以前的 task ID 就丟失了,沒法正確調用 end。

那怎麼判斷 0x8badf00d 究竟是主線程卡死,仍是出現了 leaked Background Task ?很簡單,看主線程的 stack,若是長這樣:

Thread 0 Crashed:0   

libsystem_kernel.dylib        0x000000018472be08 0x18472b000 + 35921   

libsystem_kernel.dylib        0x000000018472bc80 0x18472b000 + 32002   

CoreFoundation                0x0000000184c6ee40 0x184b81000 + 9744003   

CoreFoundation                0x0000000184c6c908 0x184b81000 + 9648724   

CoreFoundation                0x0000000184b8cda8 0x184b81000 + 485525   

GraphicsServices              0x0000000186b6f020 0x186b64000 + 450886   

UIKit                          0x000000018eb6d78c 0x18e850000 + 32664447   

Messenger                      0x0000000103015ee4 0x102ff8000 + 1225968   

libdyld.dylib                  0x000000018461dfc0 0x18461d000 + 4032

這個 stack 很經典,常常會看到,不須要 symbolicate 也能知道是幹啥,這是 UI 線程 runloop 處於 idle 狀態的 stack,在等待 kernel 的 message。表示 UI 線程此時處於閒置狀態,這種狀態下的系統強殺大機率是因爲 leaked Background Task 致使的。

善用設備本地的 crash 日誌

當用戶的手機遇到 crash,而你既沒法重現又在後臺找不到 crash 日誌的時候,此時你最大的但願就是手機本地的 crash 日誌了。

本地日誌位於 Settings -> Privacy -> Analytics -> Analytics Data。打開看下,說不定你所開發的 App 的 crash 日誌,我手機上微信和支付寶都有好些日誌。

日誌排序先是按照 App 的名稱,再按日誌發生的日期。

若是調查內存使用過多的 crash,能夠查看 JetsamEvent-xxx 開頭的日誌。

若是想知道 App 發生 crash 前系統有哪些異常日誌,須要首先在設備上安裝一個 loggingiOS.mobileconfig 的文件,這個文件基本上就是讓用戶受權給你記錄系統行爲,用戶在遇到 crash 的時候,同時按下兩個音量鍵 + 電源鍵,鬆手震動以後,系統會將過去一段時間的關鍵日誌記錄下來,對於分析一些疑難雜症頗有幫助,這種日誌通常爲 sysdiagnose_xxx 開頭。

安裝上述 loggingiOS.mobileconfig 文件以後,還有另一個好處,Apple 會記錄更多並且更詳細的 crash 日誌了,由於用戶受權過,因此 Apple 能夠大膽施爲了。這類日誌的文件名通常爲:stacks + appName - date.ips。

若是用戶的設備能重現你所調查的問題,還有另外一個簡單高效的辦法,將手機 usb 鏈接 mac,而後啓動 mac 上的 Console App,就能直觀的看到全部系統關鍵日誌了,好比網絡異常日誌能夠查看 nsurlsessiond,定位異常日誌查看 locationd,Background Task 異常日誌能夠查看 assertiond,也能夠直接按照你 app 的進程名進行過濾,查看生命週期以及被強殺的緣由。

相關文章
相關標籤/搜索