大多数iOSApp在进入后台后都会将一些关键任务封装到BackgroundTasks中,否则程序会在几秒后被系统挂起。启动后台任务后,您有3分钟时间继续执行代码。最近在排查Messenger的BackgroundCrash问题,最后追查到BackgroundTask。我想与您分享一些要点。崩溃信号通常,应用程序都有自己的崩溃日志收集工具。这些工具通常存在三个问题。一是无法抓取工具启动前的crashlog,二是app开始crash无法上传log,三是在某些特殊场景下抓不到crash信号。要解决第一个问题,只要尽可能提前工具的执行时间,或者保证之前的代码尽可能简单可靠即可。解决第二个问题,可以使用我之前分享的NSURLSession的后台模式。解决第三个问题,需要依靠苹果自己的崩溃信号,这也是很多开发团队所忽略的。苹果也有自己的崩溃日志收集,但是基于用户隐私的考虑,这个崩溃日志并不可靠,主要有以下缺陷:上传和共享数据需要用户同意。据报道,同意的比例不到20%,因此无法准确判断碰撞的实际撞击面。crashlog工具很简单,通过Xcode->Organizer打开,选择app从苹果后台下载某个版本的crashlog。不能按某种条件过滤,比如不能过滤掉所有的SIGKILL日志。日志不完整,苹果按照自己的规则给出了崩溃样本。一个app其实网上有很多crash,但苹果只列出了几十个crash样本,规律不明。崩溃日志只保存一周,每周刷新一次。比较明智的做法是写个脚本同步一下,上传到自己的后台。BackgroundTaskFancycrashBackgroundTask的API极其简单,begin和end之间的代码都进入了BackgroundTask的范畴。但是简单的代码隐藏了很多风险。以下三种崩溃情况比较容易发生。而且,这3个crash都无法被客户端内置的crash收集工具捕捉到,只能通过苹果的crashlog获取信号。原因很简单。当这些崩溃发生时,应用程序一般处于挂起状态,没有机会执行任何代码。系统直接发送SIGKILL信号后,app会被强行杀掉,同时会产生系统日志,只有苹果才能访问的日志。用户首先同意上传和分享。0xdead10cc这个crash日志一般长这样:ExceptionType:EXC_CRASH(SIGKILL)ExceptionCodes:0x0000000000000000,0x0000000000000000ExceptionNote:EXC_CORPSE_NOTIFYTerminationReason:NamespaceSPRINGBOARD,Code0xdead10ccTerminationDescription:SPRINGBOARD,com.xxx.xxxwastask-suspendedwithlockedsystemfile原因我之前介绍过,当你的App有Extension,而且Extension需要与主机App共享数据。一般的做法是将db文件放在共享容器目录下。这个时候你的App很有可能会出现这个crash。App进入后台运行后台任务。结束后,App被系统挂起。如果suspend后有任何访问db的操作,App会立即被系统kill掉。这是Apple出于保护数据库文件完整性的考虑。因此,正确的做法是将App进入后台后可能发生的所有db操作封装到BackgroundTask中,以确保安全。把这段代码写在db层可能更合适。并且苹果建议,当你要启动一个后台任务时,其实不需要考虑当前App是前台还是后台。即使App在前台启动了后台任务,进入后台后3分钟内也不会占用配额,不用担心。将关键代码放入后台任务。0xbada5e47当你按照上面的建议把尽可能多的关键代码慷慨地封装到后台任务中时,那么你可能会遇到以下崩溃:同理,因为苹果认为你启动了太多的后台任务,所以你要杀掉它们。多少是太多了?几十个不多,现在的门槛是1000个,超过1000个就会被强制击杀。如果你的BackgroundTask封装发生在db层,当有大量数据进来需要存储或者读取的时候,还是有可能会被hit的。0xbada5e47的另一个可能原因是后台任务会在超时后调用到期处理程序。不管你有多少BackgroundTasks,所有expiryhandler的执行时间都不能超过几秒,一旦超过就会被shot。因此,避免在过期处理程序中进行任何耗时的操作,例如磁盘io。0x8badf00d说到0x8badf00d,大家都不陌生。当你的主线程卡住时间过长时,系统看门狗会强行杀死你的应用程序并生成一个带有0x8badf00d的崩溃日志。BackgroundTask其实也可以0x8badf00d的,比如:ExceptionType:EXC_CRASH(SIGKILL)ExceptionCodes:0x0000000000000000,0x0000000000000000ExceptionNote:EXC_CORPSE_NOTIFYTerminationReason:NamespaceSPRINGBOARD,Code0x8badf00d当你的代码逻辑会产生leakedBackgroundTask时,就会出现上面的系统强杀crash日志了,WhatisaleakedBackgroundTask?Lookatthecode:-(void)startBgTask{self.bgTaskID=[[UIApplicationsharedApplication]beginBackgroundTaskWithExpirationHandler:^{NSLog(@"Expired:%lu",(unsignedlong)self.bgTaskID);[[UIApplicationsharedApplication]endBackgroundTask:self.bgTaskID];}];}-(void)endBgTask{[[UIApplicationsharedApplication]endBackgroundTask:self.bgTaskID];}IftheabovecodeexecutesstartBgTasktwice,therewillbealeakedBackgroundTask,becauseself.bgTaskIDwillbegivenanewIDforthesecondtime,theprevioustaskIDwillbelost,andendcannotbecalledcorrectly.那怎么判断0x8badf00d到底是主线程卡死,还是出现了leakedBackgroundTask?很简单,看主线程的stack,如果长这样:Thread0Crashed:0libsystem_kernel.dylib0x000000018472be080x18472b000+35921libsystem_kernel.dylib0x000000018472bc800x18472b000+32002CoreFoundation0x0000000184c6ee400x184b81000+9744003CoreFoundation0x0000000184c6c9080x184b81000+9648724CoreFoundation0x0000000184b8cda80x184b81000+485525GraphicsServices0x0000000186b6f0200x186b64000+450886UIKit0x000000018eb6d78c0x18e850000+32664447Messenger0x0000000103015ee40x102ff8000+1225968libdyld.dylib0x000000018461dfc00x18461d000+4032这个stack很经典,经常会看到,不需要symbolicate也能知道是干啥,这是UI线程runloop处于idle状态的stack,在等待kernel的message.IndicatesthattheUIthreadisidleatthistime,andthesystemkillinginthisstateislikelytobecausedbytheleakedBackgroundTask.MakegooduseofthelocalcrashlogofthedeviceWhentheuser'smobilephoneencountersacrash,andyoucanneitherreproducenorfindthecrashloginthebackground,yourgreatesthopeatthistimeisthelocalcrashlogofthemobilephone.LocallogsarelocatedatSettings->Privacy->Analytics->AnalyticsData.Openitandtakealook,maybethecrashlogoftheappyoudeveloped,therearemanylogsonWeChatandAlipayonmymobilephone.Thelogsaresortedfirstbythenameoftheapp,andthenbythedatethelogoccurred.如果您调查使用过多内存的崩溃,您可以查看以JetsamEvent-xxx开头的日志。如果你想知道应用崩溃前系统有哪些异常日志,你需要先在设备上安装一个loggingiOS.mobileconfig文件。这个文件基本上允许用户授权你记录系统行为。当用户遇到死机时,按下两个音量键+电源键,松开震动后,系统会记录下过去一段时间的按键日志,对于分析一些疑难杂症很有帮助。此日志通常以sysdiagnose_xxx开头。安装上面的loggingiOS.mobileconfig文件后,还有一个好处。苹果会记录越来越详细的崩溃日志。因为用户授权了,所以苹果可以大胆的去做。这类日志的文件名一般为:stacks+appName-date.ips。如果用户的设备可以重现您正在调查的问题,还有另一种简单有效的方法。将手机的usb连接到mac,然后在mac上启动ConsoleApp,可以直观的看到所有的系统关键日志,比如network异常日志可以查看nsurlsessiond,定位异常日志可以查看locationd,assertiond可以查看BackgroundTask异常日志,或者直接按你app的进程名过滤查看生命周期和被强制kill的原因。综上所述,以上就是近期排查后台任务崩溃的一些知识点分享,希望对大家有所帮助。
