天天看点

IOS崩溃 异常处理(NSSetUncaughtExceptionHandler)

  1. iOS已发布应用中对异常信息捕获和处理  
  2. 代码下载地址:http://download.csdn.net/detail/daiyelang/6740205  
  3. iOS开发中我们会遇到程序抛出异常退出的情况,如果是在调试的过程中,异常的信息是一目了然,但是如果是在已经发布的程序中,获取异常的信息有时候是比较困难的。  
  4. iOS提供了异常发生的处理API,我们在程序启动的时候可以添加这样的Handler,这样的程序发生异常的时候就可以对这一部分的信息进行必要的处理,适时的反馈给开发者。  
  5. 不足的地方是,并不是所有的程序崩溃都是由于发生可以捕捉的异常的,有些时候是因为内存等一些其他的错误导致程序的崩溃,这样的信息是不在这里体现的。  
  6. 我做了一个简单的类,进行很基本的操作,可以添加和获取Handler,捕获到异常后将信息写入到app的Documens下的Exception.txt中。  
  7. 其实还有很多的处理的办法。  
  8. l  比如可以在程序下一次起来的时候读取这个异常文件发生到服务端。  
  9. l  或者直接就是在处理代码中用openurl的方式(mailto:)调用发送邮件的方式,将异常信息直接变成邮件发送到指定地址。  
  10. 以下是完整的代码实现。  
  11. 使用场景示例:  
  12. #pragma mark -  
  13. #pragma mark Application lifecycle  
  14. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {     
  15.     // Override point for customization after application launch.  
  16.     [window makeKeyAndVisible];  
  17.      [NdUncaughtExceptionHandler setDefaultHandler];  
  18.      NSArray *array = [NSArray arrayWithObject:@"there is only one objective in this arary,call index one, app will crash and throw an exception!"];  
  19.      NSLog(@"%@", [array objectAtIndex:1]);  
  20.      return YES;  
  21. }  
  22. 基本接口展示:  
  23. #import <Foundation/Foundation.h>  
  24. @interface NdUncaughtExceptionHandler : NSObject {  
  25. }  
  26. + (void)setDefaultHandler;  
  27. + (NSUncaughtExceptionHandler*)getHandler;  
  28. @end  
  29. //还可以选择设置自定义的handler,让用户取选择  
  30. 接口实现展示  
  31. #import "NdUncaughtExceptionHandler.h"  
  32. NSString *applicationDocumentsDirectory() {  
  33.     return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];  
  34. }  
  35. void UncaughtExceptionHandler(NSException *exception) {  
  36.      NSArray *arr = [exception callStackSymbols];  
  37.      NSString *reason = [exception reason];  
  38.      NSString *name = [exception name];  
  39.      NSString *url = [NSString stringWithFormat:@"=============异常崩溃报告=============\nname:\n%@\nreason:\n%@\ncallStackSymbols:\n%@",  
  40.                    name,reason,[arr componentsJoinedByString:@"\n"]];  
  41.      NSString *path = [applicationDocumentsDirectory() stringByAppendingPathComponent:@"Exception.txt"];  
  42.      [url writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];  
  43.      //除了可以选择写到应用下的某个文件,通过后续处理将信息发送到服务器等  
  44.      //还可以选择调用发送邮件的的程序,发送信息到指定的邮件地址  
  45.      //或者调用某个处理程序来处理这个信息  
  46. }  
  47. @implementation NdUncaughtExceptionHandler  
  48. -(NSString *)applicationDocumentsDirectory {  
  49.     return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];  
  50. }  
  51. + (void)setDefaultHandler  
  52. {  
  53.      NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);  
  54. }  
  55. + (NSUncaughtExceptionHandler*)getHandler  
  56. {  
  57.      return NSGetUncaughtExceptionHandler();  
  58. }  
  59. @end  
  60. 异常崩溃报告:  
  61. =============异常崩溃报告=============  
  62. name:  
  63. NSRangeException  
  64. reason:  
  65. *** -[NSArray objectAtIndex:]: index 1 beyond bounds [0 .. 0]  
  66. callStackSymbols:  
  67. 0   CoreFoundation                      0x02393919 __exceptionPreprocess + 185  
  68. 1   libobjc.A.dylib                     0x024e15de objc_exception_throw + 47  
  69. 2   CoreFoundation                      0x0238958c -[__NSArrayI objectAtIndex:] + 236  
  70. 3   UncaughtE                           0x000022e8 -[UncaughtEAppDelegate application:didFinishLaunchingWithOptions:] + 157  
  71. 4   UIKit                               0x002b8543 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163  
  72. 5   UIKit                               0x002ba9a1 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 346  
  73. 6   UIKit                               0x002c4452 -[UIApplication handleEvent:withNewEvent:] + 1958  
  74. 7   UIKit                               0x002bd074 -[UIApplication sendEvent:] + 71  
  75. 8   UIKit                               0x002c1ac4 _UIApplicationHandleEvent + 7495  
  76. 9   GraphicsServices                    0x02bf9afa PurpleEventCallback + 1578  
  77. 10  CoreFoundation                      0x02374dc4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52  
  78. 11  CoreFoundation                      0x022d5737 __CFRunLoopDoSource1 + 215  
  79. 12  CoreFoundation                      0x022d29c3 __CFRunLoopRun + 979  
  80. 13  CoreFoundation                      0x022d2280 CFRunLoopRunSpecific + 208  
  81. 14  CoreFoundation                      0x022d21a1 CFRunLoopRunInMode + 97  
  82. 15  UIKit                               0x002ba226 -[UIApplication _run] + 625  
  83. 16  UIKit                               0x002c5b58 UIApplicationMain + 1160  
  84. 17  UncaughtE                           0x00002228 main + 102  
  85. 18  UncaughtE                           0x000021b9 start + 53  
  86. 不足的地方是,并不是所有的程序崩溃都是由于发生可以捕捉的异常的,有些时候引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了,因为这种错误它抛出的是Signal,所以必须要专门做Signal处理。首先定义一个UncaughtExceptionHandler类,.h头文件的代码如下:  
  87. UncaughtExceptionHandler类,.h头文件的代码如下:  
  88. 1  
  89. 2  
  90. 3  
  91. 4  
  92. 5  
  93. 6  
  94. #import  
  95. @interface UncaughtExceptionHandler : NSObject{  
  96. BOOL dismissed;  
  97. }  
  98. @end  
  99. 1  
  100. void InstallUncaughtExceptionHandler();  
  101. 然后在.mm文件实现InstallUncaughtExceptionHandler(),如下:  
  102. void InstallUncaughtExceptionHandler(){  
  103. signal(SIGABRT, MySignalHandler);  
  104. signal(SIGILL, MySignalHandler);  
  105. signal(SIGSEGV, MySignalHandler);  
  106. signal(SIGFPE, MySignalHandler);  
  107. signal(SIGBUS, MySignalHandler);  
  108. signal(SIGPIPE, MySignalHandler);  
  109. }  
  110. 这样,当应用发生错误而产生上述Signal后,就将会进入我们自定义的回调函数MySignalHandler。为了得到崩溃时的现场信息,还可以加入一些获取CallTrace及设备信息的代码,.mm文件的完整代码如下:  
  111. #import "UncaughtExceptionHandler.h"  
  112. #include #include  
  113. NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";  
  114. NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";  
  115. NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";  
  116. volatile int32_t UncaughtExceptionCount = 0;  
  117. const int32_t UncaughtExceptionMaximum = 10;  
  118. const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;  
  119. const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;  
  120. @implementation UncaughtExceptionHandler  
  121. + (NSArray *)backtrace  
  122. {  
  123.         void* callstack[128];  
  124.      int frames = backtrace(callstack, 128);  
  125.      charchar **strs = backtrace_symbols(callstack, frames);       
  126.      int i;  
  127.      NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];  
  128.      for (  
  129.         i = UncaughtExceptionHandlerSkipAddressCount;  
  130.         i < UncaughtExceptionHandlerSkipAddressCount +  
  131.             UncaughtExceptionHandlerReportAddressCount;  
  132.         i++)  
  133.      {  
  134.         [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];  
  135.      }  
  136.      free(strs);      
  137.      return backtrace;  
  138. }  
  139. - (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex  
  140. {  
  141.     if (anIndex == 0)  
  142.     {  
  143.         dismissed = YES;  
  144.     }  
  145. }  
  146. - (void)handleException:(NSException *)exception  
  147. {  
  148.     UIAlertView *alert =  
  149.         [[[UIAlertView alloc]  
  150.             initWithTitle:NSLocalizedString(@"Unhandled exception", nil)  
  151.             message:[NSString stringWithFormat:NSLocalizedString(  
  152.                 @"You can try to continue but the application may be unstable.\n"  
  153.                 @"%@\n%@", nil),  
  154.                 [exception reason],  
  155.                 [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]  
  156.             delegate:self  
  157.             cancelButtonTitle:NSLocalizedString(@"Quit", nil)  
  158.             otherButtonTitles:NSLocalizedString(@"Continue", nil), nil nil]  
  159.         autorelease];  
  160.     [alert show];     
  161.     CFRunLoopRef runLoop = CFRunLoopGetCurrent();  
  162.     CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);     
  163.     while (!dismissed)  
  164.     {  
  165.         for (NSString *mode in (NSArray *)allModes)  
  166.         {  
  167.             CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);  
  168.         }  
  169.     }    
  170.     CFRelease(allModes);  
  171.     NSSetUncaughtExceptionHandler(NULL);  
  172.     signal(SIGABRT, SIG_DFL);  
  173.     signal(SIGILL, SIG_DFL);  
  174.     signal(SIGSEGV, SIG_DFL);  
  175.     signal(SIGFPE, SIG_DFL);  
  176.     signal(SIGBUS, SIG_DFL);  
  177.     signal(SIGPIPE, SIG_DFL);     
  178.     if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName])  
  179.     {  
  180.  <span style="white-space:pre"> </span>kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);  
  181.     }  
  182.     else  
  183.     {  
  184.         [exception raise];  
  185.     }  
  186. }  
  187. @end  
  188. NSString* getAppInfo() { NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\nUDID : %@\n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], [UIDevice currentDevice].model, [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion, [UIDevice currentDevice].uniqueIdentifier]; NSLog(@"Crash!!!! %@", appInfo); return appInfo; } void MySignalHandler(int signal) { int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount); if (exceptionCount > UncaughtExceptionMaximum)  
  189. {  
  190. return;  
  191. }  
  192. NSMutableDictionary *userInfo =  
  193. [NSMutableDictionary  
  194. dictionaryWithObject:[NSNumber numberWithInt:signal]  
  195. forKey:UncaughtExceptionHandlerSignalKey];  
  196. NSArray *callStack = [UncaughtExceptionHandler backtrace];  
  197. [userInfo  
  198. setObject:callStack  
  199. forKey:UncaughtExceptionHandlerAddressesKey];  
  200. [[[[UncaughtExceptionHandler alloc] init] autorelease]  
  201. performSelectorOnMainThread:@selector(handleException:)  
  202. withObject:  
  203. [NSException  
  204. exceptionWithName:UncaughtExceptionHandlerSignalExceptionName  
  205. reason:  
  206. [NSString stringWithFormat:  
  207. NSLocalizedString(@"Signal %d was raised.\n"  
  208. @"%@", nil),  
  209. signal, getAppInfo()]  
  210. userInfo:  
  211. [NSDictionary  
  212. dictionaryWithObject:[NSNumber numberWithInt:signal]  
  213. forKey:UncaughtExceptionHandlerSignalKey]]  
  214. waitUntilDone:YES];  
  215. }  
  216. void InstallUncaughtExceptionHandler()  
  217. {  
  218. signal(SIGABRT, MySignalHandler);  
  219. signal(SIGILL, MySignalHandler);  
  220. signal(SIGSEGV, MySignalHandler);  
  221. signal(SIGFPE, MySignalHandler);  
  222. signal(SIGBUS, MySignalHandler);  
  223. signal(SIGPIPE, MySignalHandler);  
  224. }  
  225. 在应用自身的 didFinishLaunchingWithOptions 前,加入一个函数:  
  226. - (void)installUncaughtExceptionHandler  
  227. {  
  228. InstallUncaughtExceptionHandler();  
  229. }  
  230. 最后,在 didFinishLaunchingWithOptions 中加入这一句代码就行了:  
  231. 1  
  232. [self InstallUncaughtExceptionHandler];  
  233. 现在,基本上所有崩溃都能Hold住了。崩溃时将会显示出如下的对话框:  
  234. iOS开发23:通过归档永久存储数据 - 双子座的个人页面 - 开源中国社区  
  235. 这样在崩溃时还能从容地弹出对话框,比起闪退来,用户也不会觉得那么不爽。然后在下次启动时还可以通过邮件来发送Crash文件到邮箱,这就看各个应用的需求了。