在iOS开发中,Crash无疑是App的致命杀手。作为一个严谨的iOS 开发人员来说,写出优秀的健硕的无Crash代码至关重要。但是随着工程代码量的提升,功能的迭代,以及协作开发的模式,难免会有Crash的发生。在发生Crash时,我们应迅速定位问题,解决问题,将Crash几率降到最低。
Crash 收集
当程序运行发生Crash的时候,系统会把运行的最后时刻的程序运行记录保存下来,存储到一个.crash文件中,也就是我们常说的Crash日志。
一、 在开发中,最常遇到的Crash就是Debug状态下了,此时我们是幸运的,因为这个bug我们自己最先知道,我们可以在别人发现它之前把它改好。而且,Xcode 会提供给我们最明确的Crash信息,直接定位到Crash 的那行代码,并会打印出Crash Reason 及调用栈信息。
二、如果我们运气稍差一些,在开发中自测App没有发现任何Crash问题,但是测试童鞋或者其他部门的童鞋在测试使用中发现了Crash,但愿不要被老板发现o(╯□╰)o。无论此时的crash是必现还是非必现,我们都可以拿到测试童鞋的Crash设备,拿到设备导出Crash日志吧~
三、最难过的,无疑是自测没发现,测试童鞋也没发现,但是群众的眼睛的雪亮的,我们亲爱用户遇到了这个棘手的Crash,这极差的用户体验很有可能让用户粉转黑,怎么办?做一个Crash收集器势在必行!!!~
关于Crash收集的框架,已经有比较成熟的开源框架,KSCrash、CrashKit等,也有第三方的Crash统计产品,如Crashlytics,Hockeyapp,友盟,Bugly等等。
当App发生Crash时要上传Crash日志,之后我们可以通过服务端自己的Crash收集器拿到Crash文件,或者借助第三方服务拿到Crash文件。
2 Crash 分析
拿到了Crash日志,我们该从何入手呢?
Crash日志会提供给我们很多信息,我们要在其中提取出来可以帮我们快速定位问题的信息。
首先我们看到这几行信息,
ncident Identifier:崩溃报告的唯一标识符,不同的Crash日志该标示符也不同。
CrashReporter Key:设备标识相对应的唯一键值(并非真正的设备的UDID,苹果为了保护用户隐私iOS6以后已经无法获取)。通常同一个设备上同一版本的App发生Crash时,该值都是一样的。
Hardware Model :代表发生Crash的设备类型。
Process:代表系统Crash的进程名称,通常都是我们的App的名字, [ ]里面是当时进程的ID。
Path:App的所在路径。
Identifier:我们App的Indentifier,通常为“com.xxx.yyy”,xxx代表公司的域名,yyy代表某一个App标识。
AppVersion:当前App的版本号,由Info.plist中的两个字段组成,CFBundleShortVersionString and CFBundleVersion。
Code Type:当前App的CPU架构。
Parent Process:当前进程的父进程,由于iOS中App通常都是单进程的,一般父进程都是launchd。
Date/Time:发生crash的时间
Launch Time:启动App的时间
OS Version:iOS系统固件版本
Report Version:日志版本
Exception Type: 这个信息非常重要,它就像是这个crash的名字,我们知道了它的名字,解决它还难吗?
Exception Subtype:它就是crash的小名,当它的大名满足不了我们的时候,google它的小名,你一定会有收获!~
Triggered by Thread: 问题发生的thread
我们再来看线程信息,在日志中找到crash thread,问题就发生在这里,
有些情况下,Crashed Thread 的调用栈中会明确的告诉我们是执行到哪个类中哪行代码时发生了问题,这种情况下我们很容易判断问题原因以及修改问题。但是大多数情况下,调用栈里显示我们Crash在了一个系统的库里,我们看不到代码,所以没办法确定是哪里的操作造成了问题,于是,我们需要做点事情,将crash日志文件符号化。
为了解析crash日志,我们需要三个东西:
1.crash文件
2.符号文件:.dsymb格式
3.应用程序文件:.app格式
然后我们需要把这三个文件放到同一目录下,用atos命令来符号化crash日志的某一行:
打开终端,输入
xcrun atos -o appName.app/appName -arch armv7
然后再输入你要符号化的那一行后面的调用栈地址,例如:0x000000018a650b38
这样就可以得到结果:
就能够定位到具体是代码的哪一行发生了问题。
更多符号化crash文件的方法,可参考链接。http://wufawei.com/2014/03/symbolicating-ios-crash-logs/
3 Crash 处理
一、Watchdog timeout
Exception Code:0x8badf00d
可以读作“eat bad food”,我吃了坏东西,不能继续为你工作了。是不是很形象?
当我们的App 在启动、退出、或者在响应系统事件的时候等待了太长时间,系统会直接杀死进程。Its Not A Crash~
我们应该查看App是否在主线程请求了网络,或者其他耗时的事情卡住了正常初始化流程。
通常系统允许一个App从启动到可以相应用户事件的时间最多为5S,如果超过了5S,App就会被系统终止掉。在Launch,resume,suspend,quit时都会有相应的时间要求。在Highlight Thread里面我们可以看到被终止时调用到的位置,xxxAppDelegate加上行号。
PS. 在连接Xcode调试时为了便于调试,系统会暂时禁用掉Watchdog,所以此类问题的发现需要使用正常的启动模式。
二、用户强制退出
Exception Codes: 0xdeadfa11, deadfall
与正常退出杀死App不同,这种情况可能是用户强制关机或系统强制关机等造成。
三、低内存闪退
当系统发生低内存闪退时,很有可能我们拿不到任何的Crash信息日志,但App的的确确是闪退了。好好做下检讨吧,是不是哪里有内存泄露?用工具好好测试下。
如果我们能够拿到日志,会发现它和一般的Crash日志不太一样,通常有Free pages,Wired Pages,Purgeable pages,largest process 组成,同时会列出当前系统调用栈信息。
如果我们用的是MRC,首先静态分析一下,是不是哪里忘记了release ? dealloc 写的是否正确? 然后使用Instruments检查下内存使用情况,看看哪里的内存占用较高?是否内存泄露?通常大量的图片不能及时释放内存空间的时候会使内存占用飚升。
内存警告通常在我们debug的时候就会发现,及时的清理掉不用的内存,否则内存占用越来越高,超过系统限制就会被系统杀死。
四、Crash due to bugs
因为程序bug导致的Crash通常千奇百怪,很难一概而论。大部分情况通过Crash日志就可以定位出问题,当然也不排除部分疑难杂症看半天都不值问题出在哪儿。这个就只能看功底了,一点点找,总是能发现蛛丝马迹。是在看不出来时还可以求助于Google大神,总有人遇到和你一样的Bug
五、Exception Type
1)EXC_BAD_ACCESS
此类型的Excpetion是我们最长碰到的Crash,通常用于访问了不改访问的内存导致。一般EXC_BAD_ACCESS后面的"()"还会带有补充信息。
SIGSEGV:通常由于重复释放对象导致,这种类型在切换了ARC以后应该已经很少见到了。
SIGABRT:收到Abort信号退出,通常Foundation库中的容器为了保护状态正常会做一些检测,例如插入nil到数组中等会遇到此类错误。
SEGV:(Segmentation Violation),代表无效内存地址,比如空指针,未初始化指针,栈溢出等;
SIGBUS:总线错误,与 SIGSEGV 不同的是,SIGSEGV 访问的是无效地址,而 SIGBUS 访问的是有效地址,但总线访问异常(如地址对齐问题)
SIGILL:尝试执行非法的指令,可能不被识别或者没有权限
2)EXC_BAD_INSTRUCTION
此类异常通常由于线程执行非法指令导致。
1.在代码中修改了storyboard与outlet的对应关系,但是storyboard没有更新时发生过此crash。
2.与第三方库中方法冲突时发生过此crash。
3.调用系统方法时传入了不恰当的指针类型。
3)EXC_ARITHMETIC
代码中做除法时分母为零了会发生此问题。
6、Exception Code
0xbaaaaaad此种类型的log意味着该Crash log并非一个真正的Crash,它仅仅只是包含了整个系统某一时刻的运行状态。通常可以通过同时按Home键和音量键,可能由于用户不小心触发
0xbad22222当VOIP程序在后台太过频繁的激活时,系统可能会终止此类程序
0x8badf00d这个前面已经介绍了,程序启动或者恢复时间过长被watch dog终止
0xc00010ff程序执行大量耗费CPU和GPU的运算,导致设备过热,触发系统过热保护被系统终止
0xdead10cc程序退到后台时还占用系统资源,如通讯录被系统终止
0xdeadfa11**前面也提到过,程序无响应用户强制关闭
更多开发中遇到的错误码可以参考链接https://en.wikipedia.org/wiki/Hexspeak