iOS后臺任務(wù)崩潰處理方式

iOS App 后臺任務(wù)的坑

大多數(shù) iOS App 在進(jìn)入后臺之后都會將一些關(guān)鍵任務(wù)封裝到 Background Task 里庇绽,否則程序在若干秒之后就會被系統(tǒng) Suspend蛤虐。啟動 Background Task 之后,可以獲得 3 分鐘繼續(xù)執(zhí)行代碼的時間温赔。

最近在調(diào)查 Messenger 的 Background Crash 問題蒲每,最后都追蹤到和 Background Task 相關(guān)净薛,和大家分享下一些要點瘩例。

Crash 信號

一般 App 都有自己的 crash 日志采集工具,這類工具一般有三個問題赦拘。第一是在工具啟動之前的 crash 日志無法捕捉慌随,第二是如果 App 啟動閃退日志無法上傳,第三是一些特殊場景的系統(tǒng)強(qiáng)殺無法捕捉 crash 信號躺同。

解決第一個問題阁猜,只要將工具的執(zhí)行時間盡可能提前,或者確保之前的代碼及可能簡單可靠蹋艺。

解決第二個問題剃袍,可以采用我之前分享過的,使用 NSURLSession 的 background mode捎谨。

解決第三個問題民效,需要依賴于 Apple 自己的 crash 信號,這也是很多開發(fā)團(tuán)隊所忽視的一點涛救。

Apple 也有自己的 crash 日志采集畏邢,不過基于用戶隱私的考慮,這個 crash 日志并不可靠检吆,主要存在以下幾方面的缺陷:

?用戶需同意上傳并分享數(shù)據(jù)舒萎,據(jù)聞,同意比例不足 20%蹭沛,所以無法準(zhǔn)確確定某個 crash 的實際影響面臂寝。

?crash 日志工具簡陋章鲤,通過 Xcode -> Organizer 打開,選中 App 就能從 Apple 后臺下載某個版本的 crash 日志咆贬,無法通過某個條件做篩選败徊,比如你不能過濾出所有 SIGKILL 的日志。

?日志不全素征,Apple 按照自己的規(guī)則呈現(xiàn) crash 樣本集嵌,一個 App 實際線上的 crash 非常之多萝挤,但 Apple 列出的 crash 樣本只有數(shù)十個御毅,規(guī)則不明。

?crash 日志只保存一周怜珍,一周刷新一次端蛆,所有比較明智的做法是寫個腳本同步下來,上傳到自己的后臺酥泛。

Background Task 花式 crash

Background Task 的 API 及其簡單今豆,begin 和 end 之間的代碼全部進(jìn)入 Background Task 的范疇。但簡單的代碼隱藏著不小的風(fēng)險柔袁,下面列出三個比較容易出現(xiàn)的 crash呆躲。而且這三個 crash 都是客戶端自帶的 crash 采集工具無法捕捉的,只能通過 Apple 的 crash 日志獲得信號捶索。原因很簡單插掂,這些 crash 發(fā)生的時候 app 一般處于 suspend 狀態(tài),根本沒有機(jī)會執(zhí)行任何代碼腥例,系統(tǒng)直接發(fā)送 SIGKILL 信號后就將 app 強(qiáng)殺辅甥,并生成一個系統(tǒng)日志,一個只能 Apple 訪問的日志燎竖,還得用戶先同意上傳分享璃弄。

0xdead10cc

這個 crash 日志一般長這樣:

Exception Type: EXC_CRASH (SIGKILL) Exception Codes: 0x0000000000000000, 0x0000000000000000 Exception Note: EXC_CORPSE_NOTIFY Termination Reason: Namespace SPRINGBOARD, Code 0xdead10cc

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

原因我之前介紹過,當(dāng)你的 App 有 Extension构回,而且 Extension 存在和 Host App 共享數(shù)據(jù)的需求夏块,一般做法會將 db 文件放入 shared container 目錄下,此時你的 App 就有大概率會發(fā)生這種 crash纤掸。

App 進(jìn)入后臺運行 Background Task拨扶,end 之后 App 被系統(tǒng) suspend,如果 suspend 之后還存在任何訪問 db 的操作茁肠,此時 App 會立馬被系統(tǒng)強(qiáng)殺患民,這是 Apple 出于保護(hù)數(shù)據(jù)庫文件的完整的考慮。

所以正確的做法是將所有有可能在 App 進(jìn)入后臺之后垦梆,還會發(fā)生的 db 操作統(tǒng)統(tǒng)封入 Background Task匹颤,以確保安全仅孩。這個代碼寫在 db layer 可能更加合適。

而且 Apple 推薦當(dāng)你想啟動 Background Task 的時候印蓖,其實并不需要考慮當(dāng)前 App 是出于 foreground 還是 background辽慕,即使 App 在前臺啟動 Background Task,也并不會占用進(jìn)入后臺之后 3 分鐘額度赦肃,所以放心大膽的把關(guān)鍵代碼放進(jìn) Background Task 吧溅蛉。

0xbada5e47

當(dāng)你聽從了上面的建議,大大方方的把盡可能多的關(guān)鍵代碼封入 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 相關(guān)船侧,原因是 Apple 認(rèn)為你啟動了過多的 Background Task,所以要殺掉厅各。多少算多呢镜撩?幾十個不多,當(dāng)前的 threshold 是 1000 個队塘,超過 1000 個才會強(qiáng)殺袁梗。如果你的 Background Task 封裝發(fā)生在 db layer,出現(xiàn)大量數(shù)據(jù)過來需要存儲或讀取的時候憔古,還是有可能會 hit 這個 limit遮怜。

另一個 0xbada5e47 的可能原因是,Background Task 在超時之后會調(diào)用 expiry handler鸿市,無論你有多少個 Background Task锯梁,所有 expiry handler 執(zhí)行的時間不能超過若干秒,一旦超過也會被槍殺灸芳。所以在 expiry handler 里面切忌有任何比如 disk io 的耗時操作涝桅。

0x8badf00d

說到 0x8badf00d,大家都很熟悉了烙样,當(dāng)你的主線程卡住的時間太長冯遂,系統(tǒng)的 Watchdog 會將你的 App 強(qiáng)殺,并生成一個帶有 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

當(dāng)你的代碼邏輯會產(chǎn)生 leaked Background Task 時,就會出現(xiàn)上面的系統(tǒng)強(qiáng)殺 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 執(zhí)行兩次,就一定會出現(xiàn) leaked Background Task赔硫,因為 self.bgTaskID 第二次會被賦予一個新的 ID炒俱,之前的 task ID 就丟失了,無法正確調(diào)用 end。

那怎么判斷 0x8badf00d 到底是主線程卡死权悟,還是出現(xiàn)了 leaked Background Task 砸王?很簡單,看主線程的 stack峦阁,如果長這樣:

Thread 0 Crashed: 0 libsystem_kernel.dylib 0x000000018472be08 0x18472b000 + 3592 1 libsystem_kernel.dylib 0x000000018472bc80 0x18472b000 + 3200 2 CoreFoundation 0x0000000184c6ee40 0x184b81000 + 974400 3 CoreFoundation 0x0000000184c6c908 0x184b81000 + 964872 4 CoreFoundation 0x0000000184b8cda8 0x184b81000 + 48552 5 GraphicsServices 0x0000000186b6f020 0x186b64000 + 45088 6 UIKit 0x000000018eb6d78c 0x18e850000 + 3266444 7 Messenger 0x0000000103015ee4 0x102ff8000 + 122596 8 libdyld.dylib 0x000000018461dfc0 0x18461d000 + 4032

這個 stack 很經(jīng)典谦铃,經(jīng)常會看到,不需要 symbolicate 也能知道是干啥榔昔,這是 UI 線程 runloop 處于 idle 狀態(tài)的 stack驹闰,在等待 kernel 的 message。表示 UI 線程此時處于閑置狀態(tài)撒会,這種狀態(tài)下的系統(tǒng)強(qiáng)殺大概率是由于 leaked Background Task 導(dǎo)致的嘹朗。

善用設(shè)備本地的 crash 日志

當(dāng)用戶的手機(jī)遇到 crash,而你既無法重現(xiàn)又在后臺找不到 crash 日志的時候茧彤,此時你最大的希望就是手機(jī)本地的 crash 日志了骡显。

本地日志位于 Settings -> Privacy -> Analytics -> Analytics Data疆栏。打開看下曾掂,說不定你所開發(fā)的 App 的 crash 日志,我手機(jī)上微信和支付寶都有好些日志壁顶。

日志排序先是按照 App 的名稱珠洗,再按日志發(fā)生的日期。

如果調(diào)查內(nèi)存使用過多的 crash若专,可以查看 JetsamEvent-xxx 開頭的日志许蓖。

如果想知道 App 發(fā)生 crash 前系統(tǒng)有哪些異常日志,需要首先在設(shè)備上安裝一個 loggingiOS.mobileconfig 的文件调衰,這個文件基本上就是讓用戶授權(quán)給你記錄系統(tǒng)行為膊爪,用戶在遇到 crash 的時候,同時按下兩個音量鍵 + 電源鍵嚎莉,松手震動之后米酬,系統(tǒng)會將過去一段時間的關(guān)鍵日志記錄下來,對于分析一些疑難雜癥很有幫助趋箩,這種日志一般為 sysdiagnose_xxx 開頭赃额。

安裝上述 loggingiOS.mobileconfig 文件之后,還有另外一個好處叫确,Apple 會記錄更多而且更詳細(xì)的 crash 日志了跳芳,因為用戶授權(quán)過,所以 Apple 可以大膽施為了竹勉。這類日志的文件名一般為:stacks + appName - date.ips飞盆。

如果用戶的設(shè)備能重現(xiàn)你所調(diào)查的問題,還有另一個簡單高效的辦法,將手機(jī) usb 連接 mac吓歇,然后啟動 mac 上的 Console App车胡,就能直觀的看到所有系統(tǒng)關(guān)鍵日志了,比如網(wǎng)絡(luò)異常日志可以查看 nsurlsessiond照瘾,定位異常日志查看 locationd匈棘,Background Task 異常日志可以查看 assertiond,也可以直接按照你 app 的進(jìn)程名進(jìn)行過濾析命,查看生命周期以及被強(qiáng)殺的原因主卫。

總結(jié)

以上是最近調(diào)查 Background Task crash 的一些知識點分享,希望對大家有所幫助鹃愤。

原文鏈接:http://mrpeak.cn/blog/ios-background-task/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末簇搅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子软吐,更是在濱河造成了極大的恐慌瘩将,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凹耙,死亡現(xiàn)場離奇詭異姿现,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)肖抱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門备典,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人意述,你說我怎么就攤上這事提佣。” “怎么了荤崇?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵拌屏,是天一觀的道長。 經(jīng)常有香客問我术荤,道長倚喂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任喜每,我火速辦了婚禮务唐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘带兜。我一直安慰自己枫笛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布刚照。 她就那樣靜靜地躺著刑巧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啊楚,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天吠冤,我揣著相機(jī)與錄音,去河邊找鬼恭理。 笑死拯辙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颜价。 我是一名探鬼主播涯保,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼周伦!你這毒婦竟也來了夕春?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤专挪,失蹤者是張志新(化名)和其女友劉穎及志,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寨腔,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡速侈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了脆侮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锌畸。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡勇劣,死狀恐怖靖避,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情比默,我是刑警寧澤幻捏,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布氯庆,位于F島的核電站冗懦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏墨礁。R本人自食惡果不足惜醋奠,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一榛臼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窜司,春花似錦沛善、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春尤蛮,著一層夾襖步出監(jiān)牢的瞬間媳友,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工产捞, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留醇锚,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓坯临,卻偏偏與公主長得像搂抒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尿扯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

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