在APP用户量达到一定基数的时候,用户在使用应用的期间,或多或少的会碰到一些致使程序闪退的情况,而我们需要将这些情况收集起来。
一般情况下,应用程序发生闪退是,通常都会采用第三方平台进行统计分析,例如:

  • 1、友盟
  • 2、Flurry
  • 3、Crashlytics

而这篇博客讲的是如何利用苹果自身的sdk 【NSException】进行捕获收集这些闪退信息。

说到异常捕获,就必须要提到Crash问题,iOS中,Crash一般分为两种:

  • 1、一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;
  • 2、一种是未被捕获的目标C异常(NSException)记录,导致程序向自身发送了SIGABRT信号而崩溃。

遗憾的是,我们只能捕捉记录第二种方法,但如果我们日志记录得当,还是能够解决APP中绝大部分的崩溃问题,下面针对第二种方法做一些处理:

完整代码的下载地址:【DemoExceptionHandler

一、系统崩溃

对于系统崩溃而引起的程序异常退出,可以通过【NSSetUncaughtExceptionHandler】机制捕获,这个方法比较简单。

二、处理signal

使用Obj-C的异常处理是得 不到signal的,如果要处理它,我们还要利用unix标准的signal机制,注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数。该函数中我们可以输出栈信息,版本信息等其他一切我们所想要的。NSSetUncaughtExceptionHandler 用来做异常处理,但功能非常有限.而引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了,因为这种错误它抛出的是Signal,所以必须要专门做Signal处理。
针对以上问题,我阅读了解了多篇博客,借鉴了多家的集成,并对每个方法和属性做了注释,定义一个【UncaughtExceptionHandler】类,用来捕获处理所有的崩溃信息,方法如下:

  1. #import <Foundation/Foundation.h>
  2. #import <UIKit/UIKit.h>
  3. @interface UncaughtExceptionHandler : NSObject
  4. /*!
  5. * 异常的处理方法
  6. *
  7. * @param install 是否开启捕获异常
  8. * @param showAlert 是否在发生异常时弹出alertView
  9. */
  10. + (void)installUncaughtExceptionHandler:(BOOL)install showAlert:(BOOL)showAlert;
  11. @end
  1. #import "UncaughtExceptionHandler.h"
  2. #include <libkern/OSAtomic.h>
  3. #include <execinfo.h>
  4. //NSException错误名称
  5. NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
  6. //signal错误堆栈的条数
  7. NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
  8. //错误堆栈信息
  9. NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
  10. //初始化的错误条数
  11. volatile int32_t UncaughtExceptionCount = 0;
  12. //错误最大的条数
  13. const int32_t UncaughtExceptionMaximum = 10;
  14. ///是否弹窗提示
  15. static BOOL showAlertView = nil;
  16. //异常处理
  17. void HandleException(NSException *exception);
  18. //Signal类型错误信号处理
  19. void SignalHandler(int signal);
  20. //获取app信息
  21. NSString* getAppInfo(void);
  22. @interface UncaughtExceptionHandler()
  23. ///判断程序是否继续执行
  24. @property (assign, nonatomic) BOOL dismissed;
  25. @end
  26. @implementation UncaughtExceptionHandler
  27. /*!
  28. * 1、异常的处理方法
  29. *
  30. * @param install 是否开启捕获异常
  31. * @param showAlert 是否在发生异常时弹出alertView
  32. */
  33. + (void)installUncaughtExceptionHandler:(BOOL)install showAlert:(BOOL)showAlert {
  34. if (install && showAlert) {
  35. [[self alloc] alertView:showAlert];
  36. }
  37. NSSetUncaughtExceptionHandler(install ? HandleException : NULL);
  38. signal(SIGABRT, install ? SignalHandler : SIG_DFL);
  39. signal(SIGILL, install ? SignalHandler : SIG_DFL);
  40. signal(SIGSEGV, install ? SignalHandler : SIG_DFL);
  41. signal(SIGFPE, install ? SignalHandler : SIG_DFL);
  42. signal(SIGBUS, install ? SignalHandler : SIG_DFL);
  43. signal(SIGPIPE, install ? SignalHandler : SIG_DFL);
  44. }
  45. ///设置是否弹窗提示
  46. - (void)alertView:(BOOL)show {
  47. showAlertView = show;
  48. }
  49. #pragma mark - methond
  50. //med 1、专门针对Signal类型的错误获取堆栈信息
  51. + (NSArray *)backtrace {
  52. //指针列表
  53. void* callstack[128];
  54. //backtrace用来获取当前线程的调用堆栈,获取的信息存放在这里的callstack中
  55. //128用来指定当前的buffer中可以保存多少个void*元素
  56. //返回值是实际获取的指针个数
  57. int frames = backtrace(callstack, 128);
  58. //backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组
  59. //返回一个指向字符串数组的指针
  60. //每个字符串包含了一个相对于callstack中对应元素的可打印信息,包括函数名、偏移地址、实际返回地址
  61. char **strs = backtrace_symbols(callstack, frames);
  62. int i;
  63. NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
  64. for (i = 0; i < frames; i++) {
  65. [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
  66. }
  67. free(strs);
  68. return backtrace;
  69. }
  70. //med 2、所有错误异常处理
  71. - (void)handleException:(NSException *)exception {
  72. //验证和保存错误数据
  73. [self validateAndSaveCriticalApplicationData:exception];
  74. ///错误弹窗提示设置
  75. if (!showAlertView) {
  76. return;
  77. }
  78. #pragma clang diagnostic push
  79. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  80. UIAlertView *alert =
  81. [[UIAlertView alloc]
  82. initWithTitle:@"出错啦"
  83. message:[NSString stringWithFormat:@"你可以尝试继续操作,但是应用可能无法正常运行.\n"]
  84. delegate:self
  85. cancelButtonTitle:@"退出"
  86. otherButtonTitles:@"继续", nil];
  87. [alert show];
  88. #pragma clang diagnostic pop
  89. CFRunLoopRef runLoop = CFRunLoopGetCurrent();
  90. CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
  91. while (!self.dismissed) {
  92. //点击继续
  93. for (NSString *mode in (__bridge NSArray *)allModes) {
  94. //快速切换Mode
  95. CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
  96. }
  97. }
  98. //点击退出
  99. CFRelease(allModes);
  100. NSSetUncaughtExceptionHandler(NULL);
  101. signal(SIGABRT, SIG_DFL);
  102. signal(SIGILL, SIG_DFL);
  103. signal(SIGSEGV, SIG_DFL);
  104. signal(SIGFPE, SIG_DFL);
  105. signal(SIGBUS, SIG_DFL);
  106. signal(SIGPIPE, SIG_DFL);
  107. if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName]) {
  108. kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
  109. } else {
  110. [exception raise];
  111. }
  112. }
  113. //点击退出
  114. #pragma clang diagnostic push
  115. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  116. - (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex {
  117. #pragma clang diagnostic pop
  118. if (anIndex == 0) {
  119. self.dismissed = YES;
  120. }
  121. }
  122. //验证和保存错误数据
  123. - (void)validateAndSaveCriticalApplicationData:(NSException *)exception {
  124. NSString *exceptionInfo = [NSString stringWithFormat:@"\n--------Log Exception---------\nappInfo :\n%@\n\nexception name :%@\nexception reason :%@\nexception userInfo :%@\ncallStackSymbols :%@\n\n--------End Log Exception-----", getAppInfo(),exception.name, exception.reason, exception.userInfo ? : @"no user info", [exception callStackSymbols]];
  125. NSLog(@"%@", exceptionInfo);
  126. ///写入文件
  127. // [exceptionInfo writeToFile:[NSString stringWithFormat:@"%@/Documents/error.log",NSHomeDirectory()] atomically:YES encoding:NSUTF8StringEncoding error:nil];
  128. }
  129. @end
  130. ///2.1、奔溃异常处理
  131. void HandleException(NSException *exception) {
  132. int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
  133. // 如果太多不用处理
  134. if (exceptionCount > UncaughtExceptionMaximum) {
  135. return;
  136. }
  137. //获取调用堆栈
  138. NSArray *callStack = [exception callStackSymbols];
  139. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
  140. [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
  141. //在主线程中,执行制定的方法, withObject是执行方法传入的参数
  142. [[[UncaughtExceptionHandler alloc] init]
  143. performSelectorOnMainThread:@selector(handleException:)
  144. withObject:
  145. [NSException exceptionWithName:[exception name]
  146. reason:[exception reason]
  147. userInfo:userInfo]
  148. waitUntilDone:YES];
  149. }
  150. //2.2、signal报错处理
  151. void SignalHandler(int signal) {
  152. int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
  153. // 如果太多不用处理
  154. if (exceptionCount > UncaughtExceptionMaximum) {
  155. return;
  156. }
  157. NSString* description = nil;
  158. switch (signal) {
  159. case SIGABRT:
  160. description = [NSString stringWithFormat:@"Signal SIGABRT was raised!\n"];
  161. break;
  162. case SIGILL:
  163. description = [NSString stringWithFormat:@"Signal SIGILL was raised!\n"];
  164. break;
  165. case SIGSEGV:
  166. description = [NSString stringWithFormat:@"Signal SIGSEGV was raised!\n"];
  167. break;
  168. case SIGFPE:
  169. description = [NSString stringWithFormat:@"Signal SIGFPE was raised!\n"];
  170. break;
  171. case SIGBUS:
  172. description = [NSString stringWithFormat:@"Signal SIGBUS was raised!\n"];
  173. break;
  174. case SIGPIPE:
  175. description = [NSString stringWithFormat:@"Signal SIGPIPE was raised!\n"];
  176. break;
  177. default:
  178. description = [NSString stringWithFormat:@"Signal %d was raised!",signal];
  179. }
  180. NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
  181. NSArray *callStack = [UncaughtExceptionHandler backtrace];
  182. [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
  183. [userInfo setObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];
  184. //在主线程中,执行指定的方法, withObject是执行方法传入的参数
  185. [[[UncaughtExceptionHandler alloc] init]
  186. performSelectorOnMainThread:@selector(handleException:)
  187. withObject:
  188. [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
  189. reason: description
  190. userInfo: userInfo]
  191. waitUntilDone:YES];
  192. }
  193. ///获取app信息
  194. NSString* getAppInfo() {
  195. NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\n",
  196. [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
  197. [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
  198. [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
  199. [UIDevice currentDevice].model,
  200. [UIDevice currentDevice].systemName,
  201. [UIDevice currentDevice].systemVersion];
  202. return appInfo;
  203. }

最后,在didFinishLaunchingWithOptions中调用该函数

  1. #import "UncaughtExceptionHandler.h"
  2. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  3. //捕获异常
  4. [UncaughtExceptionHandler installUncaughtExceptionHandler:YES showAlert:YES];
  5. return YES;
  6. }

三、测试

以下是我的三个测试案例,前两个方法属于系统奔溃,直接编译运行,即可监听到Crash。

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. // [self exceptionHandlerTest1];
  4. //
  5. // [self exceptionHandlerTest2];
  6. [self exceptionHandlerTest3];
  7. }
  8. ///异常处理测试1
  9. -(void)exceptionHandlerTest1{
  10. //1、ios崩溃【数组越界】
  11. NSArray *array= @[@"tom",@"xxx",@"ooo"];
  12. [array objectAtIndex:5];
  13. }
  14. ///异常处理测试2
  15. -(void)exceptionHandlerTest2{
  16. //2、用来制造一些异常【不存在string的方法】
  17. [self performSelector:@selector(string) withObject:nil afterDelay:2.0];
  18. }
  19. ///异常处理测试3
  20. -(void)exceptionHandlerTest3{
  21. //3、信号量
  22. int list[2]={1,2};
  23. int *p = list;
  24. //[奔溃位置]导致SIGABRT的错误,因为内存中根本就没有这个空间,哪来的free,就在栈中的对象而已
  25. free(p);
  26. p[1] = 5;
  27. }

第三种测试属于signal类中的SIGABRT奔溃,在Xcode中测试的时候,程序不会进入bugrpt_signalHandler处理函数里面,因为Xcode屏蔽了signal的回调,为此,我们需要在【lldb】中输入以下命令,signal的回调才可以进来【pro hand -p true -s false SIGABRT】,其中,SIGABRT可以替换为你需要的任何signal类型,比如SIGSEGV。详见下图:
image.png
image.png

执行完成字后,打印的错误日志如下:

  1. *** set a breakpoint in malloc_error_break to debug
  2. 2018-09-07 13:04:58.097353 DemoExceptionHandler[266:16655]
  3. --------Log Exception---------
  4. appInfo :
  5. App : (null) 1.0(1)
  6. Device : iPhone
  7. OS Version : iOS 10.2
  8. exception name :UncaughtExceptionHandlerSignalExceptionName
  9. exception reason :Signal SIGABRT was raised!
  10. exception userInfo :{
  11. UncaughtExceptionHandlerAddressesKey = (
  12. "0 DemoExceptionHandler 0x00000001000253dc +[UncaughtExceptionHandler backtrace] + 76",
  13. "1 DemoExceptionHandler 0x0000000100025174 SignalHandler + 676",
  14. "2 libsystem_platform.dylib 0x0000000182129338 _sigtramp + 36",
  15. "3 libsystem_pthread.dylib 0x000000018212f450 pthread_kill + 112",
  16. "4 libsystem_c.dylib 0x0000000181fdb400 abort + 140",
  17. "5 libsystem_malloc.dylib 0x000000018209d944 <redacted> + 0",
  18. "6 DemoExceptionHandler 0x0000000100024a50 -[ViewController exceptionHandlerTest3] + 96",
  19. "7 DemoExceptionHandler 0x00000001000248a4 -[ViewController viewDidLoad] + 92",
  20. "8 UIKit 0x0000000188f4e924 <redacted> + 1056",
  21. "9 UIKit 0x0000000188f4e4ec <redacted> + 28",
  22. "10 UIKit 0x0000000188f54c98 <redacted> + 76",
  23. "11 UIKit 0x0000000188f52138 <redacted> + 272",
  24. "12 UIKit 0x0000000188fc468c <redacted> + 48",
  25. "13 UIKit 0x00000001891d0cb8 <redacted> + 4068",
  26. "14 UIKit 0x00000001891d6808 <redacted> + 1656",
  27. "15 UIKit 0x00000001891eb104 <redacted> + 48",
  28. "16 UIKit 0x00000001891d37ec <redacted> + 168",
  29. "17 FrontBoardServices 0x0000000184c6f92c <redacted> + 36",
  30. "18 FrontBoardServices 0x0000000184c6f798 <redacted> + 176",
  31. "19 FrontBoardServices 0x0000000184c6fb40 <redacted> + 56",
  32. "20 CoreFoundation 0x0000000183046b5c <redacted> + 24",
  33. "21 CoreFoundation 0x00000001830464a4 <redacted> + 524",
  34. "22 CoreFoundation 0x00000001830440a4 <redacted> + 804",
  35. "23 CoreFoundation 0x0000000182f722b8 CFRunLoopRunSpecific + 444",
  36. "24 UIKit 0x0000000188fb97b0 <redacted> + 608",
  37. "25 UIKit 0x0000000188fb4534 UIApplicationMain + 208",
  38. "26 DemoExceptionHandler 0x0000000100025fec main + 124",
  39. "27 libdyld.dylib 0x0000000181f555b8 <redacted> + 4"
  40. );
  41. UncaughtExceptionHandlerSignalKey = 6;
  42. }
  43. callStackSymbols :(null)
  44. --------End Log Exception-----
  45. 2018-09-07 13:04:58.280259 DemoExceptionHandler[266:16655] invalid mode 'kCFRunLoopCommonModes' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution.

四、Signal部分信号说明:

序 号 信号 发出信号原因
1 SIGHUP 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。 登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个 Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进 程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录, wget也 能继续下载。 此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
2 SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
3 SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。
4 SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
5 SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用。
6 SIGABRT 调用abort函数生成的信号。
7 SIGBUS 非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。
8 SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。
9 SIGKILL 用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
10 SIGUSR1 留给用户使用
11 SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
12 SIGUSR2 留给用户使用
13 SIGPIPE 管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
14 SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.
15 SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。
17 SIGCHLD 子进程结束时, 父进程会收到这个信号。 如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。
18 SIGCONT 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符
19 SIGSTOP 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.
20 SIGTSTP 停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号
21 SIGTTIN 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.
22 SIGTTOU 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.
23 SIGURG 有”紧急”数据或out-of-band数据到达socket时产生.
24 SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。
25 SIGXFSZ 当进程企图扩大文件以至于超过文件大小资源限制。
26 SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
27 SIGPROF 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
28 SIGWINCH 窗口大小改变时发出.
29 SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作.
30 SIGPWR Power failure
31 SIGSYS 非法的系统调用。
  • 在以上列出的信号中:

1、程序不可捕获、阻塞或忽略的信号有:SIGKILL、SIGSTOP 2、不能恢复至默认动作的信号有:SIGILL、SIGTRAP 3、默认会导致进程流产的信号有:SIGABRT、SIGBUS、SIGFPE、SIGILL、SIGIOT、SIGQUIT、SIGSEGV、SIGTRAP、SIGXCPU、SIGXFSZ 4、默认会导致进程退出的信号有: SIGALRM、SIGHUP、SIGINT、SIGKILL、SIGPIPE、SIGPOLL、SIGPROF、SIGSYS、SIGTERM、SIGUSR1、SIGUSR2、SIGVTALRM 5、默认会导致进程停止的信号有:SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU 6、默认进程忽略的信号有:SIGCHLD、SIGPWR、SIGURG、SIGWINCH 7、此外,SIGIO在SVR4是退出,在4.3BSD中是忽略; 8、SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。

要查询本机的信号表可以在终端输入【kill -l】,如下图:
image.png
其具体LinuxSignal列表,可以点击:【linux signal 列表】进一步了解。

五、Crash Callstack分析,属性说明:

1、 0x8badf00d : 在启动、终止应用或响应系统事件花费过长时间,意为“ate bad food”。 2、0xdeadfa11 : 用户强制退出,意为“dead fall”。(系统无响应时,用户按电源开关和HOME) 3、0xbaaaaaad : 用户按住Home键和音量键,获取当前内存状态,不代表崩溃 4、0xbad22222 : VoIP应用因为恢复得太频繁导致crash 5、0xc00010ff : 因为太烫了被干掉,意为“cool off” 6、0xdead10cc : 因为在后台时仍然占据系统资源(比如通讯录)被干掉,意为“dead lock”