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 的进程名进行过滤,查看生命周期以及被强杀的缘由。

相关文章
相关标签/搜索