iOS —— 發佈應用的異常信息捕獲和處理 NSSetUncaughtExceptionHandler()

  1. iOS已發佈應用中對異常信息捕獲和處理  
  2. 轉載地址: http://blog.csdn.net/daiyelang/article/details/17020211
  3.   
  4. iOS開發中我們會遇到程序拋出異常退出的情況,如果是在調試的過程中,異常的信息是一目瞭然,但是如果是在已經發布的程序中,獲取異常的信息有時候是比較困難的。  
  5.    
  6. iOS提供了異常發生的處理API,我們在程序啓動的時候可以添加這樣的Handler,這樣的程序發生異常的時候就可以對這一部分的信息進行必要的處理,適時的反饋給開發者。  
  7.    
  8. 不足的地方是,並不是所有的程序崩潰都是由於發生可以捕捉的異常的,有些時候是因爲內存等一些其他的錯誤導致程序的崩潰,這樣的信息是不在這裏體現的。  
  9.    
  10. 我做了一個簡單的類,進行很基本的操作,可以添加和獲取Handler,捕獲到異常後將信息寫入到app的Documens下的Exception.txt中。  
  11.    
  12. 其實還有很多的處理的辦法。  
  13. l  比如可以在程序下一次起來的時候讀取這個異常文件發生到服務端。  
  14. l  或者直接就是在處理代碼中用openurl的方式(mailto:)調用發送郵件的方式,將異常信息直接變成郵件發送到指定地址。  
  15.    
  16. 以下是完整的代碼實現。  
  17.    
  18. 使用場景示例:  
  19.    
  20. #pragma mark -  
  21. #pragma mark Application lifecycle  
  22.    
  23. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {     
  24.      
  25.     // Override point for customization after application launch.  
  26.       
  27.     [window makeKeyAndVisible];  
  28.      [NdUncaughtExceptionHandler setDefaultHandler];  
  29.      NSArray *array = [NSArray arrayWithObject:@"there is only one objective in this arary,call index one, app will crash and throw an exception!"];  
  30.      NSLog(@"%@", [array objectAtIndex:1]);  
  31.       
  32.      return YES;  
  33. }  
  34.    
  35. 基本接口展示:  
  36.    
  37. #import <Foundation/Foundation.h>  
  38.    
  39. @interface NdUncaughtExceptionHandler : NSObject {  
  40.    
  41. }  
  42.    
  43. + (void)setDefaultHandler;  
  44. + (NSUncaughtExceptionHandler*)getHandler;  
  45.    
  46. @end  
  47. //還可以選擇設置自定義的handler,讓用戶取選擇  
  48.    
  49. 接口實現展示  
  50. #import "NdUncaughtExceptionHandler.h"  
  51.    
  52. NSString *applicationDocumentsDirectory() {  
  53.     return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];  
  54. }  
  55.    
  56. void UncaughtExceptionHandler(NSException *exception) {  
  57.      NSArray *arr = [exception callStackSymbols];  
  58.      NSString *reason = [exception reason];  
  59.      NSString *name = [exception name];  
  60.    
  61.      NSString *url = [NSString stringWithFormat:@"=============異常崩潰報告=============\nname:\n%@\nreason:\n%@\ncallStackSymbols:\n%@",  
  62.                    name,reason,[arr componentsJoinedByString:@"\n"]];  
  63.      NSString *path = [applicationDocumentsDirectory() stringByAppendingPathComponent:@"Exception.txt"];  
  64.      [url writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];  
  65.      //除了可以選擇寫到應用下的某個文件,通過後續處理將信息發送到服務器等  
  66.      //還可以選擇調用發送郵件的的程序,發送信息到指定的郵件地址  
  67.      //或者調用某個處理程序來處理這個信息  
  68. }  
  69.    
  70. @implementation NdUncaughtExceptionHandler  
  71.    
  72. -(NSString *)applicationDocumentsDirectory {  
  73.     return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];  
  74. }  
  75.    
  76. + (void)setDefaultHandler  
  77. {  
  78.      NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);  
  79. }  
  80.    
  81. + (NSUncaughtExceptionHandler*)getHandler  
  82. {  
  83.      return NSGetUncaughtExceptionHandler();  
  84. }  
  85.    
  86. @end  
  87.    
  88. 異常崩潰報告:  
  89. =============異常崩潰報告=============  
  90. name:  
  91. NSRangeException  
  92. reason:  
  93. *** -[NSArray objectAtIndex:]: index 1 beyond bounds [0 .. 0]  
  94. callStackSymbols:  
  95. 0   CoreFoundation                      0x02393919 __exceptionPreprocess + 185  
  96. 1   libobjc.A.dylib                     0x024e15de objc_exception_throw + 47  
  97. 2   CoreFoundation                      0x0238958c -[__NSArrayI objectAtIndex:] + 236  
  98. 3   UncaughtE                           0x000022e8 -[UncaughtEAppDelegate application:didFinishLaunchingWithOptions:] + 157  
  99. 4   UIKit                               0x002b8543 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163  
  100. 5   UIKit                               0x002ba9a1 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 346  
  101. 6   UIKit                               0x002c4452 -[UIApplication handleEvent:withNewEvent:] + 1958  
  102. 7   UIKit                               0x002bd074 -[UIApplication sendEvent:] + 71  
  103. 8   UIKit                               0x002c1ac4 _UIApplicationHandleEvent + 7495  
  104. 9   GraphicsServices                    0x02bf9afa PurpleEventCallback + 1578  
  105. 10  CoreFoundation                      0x02374dc4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52  
  106. 11  CoreFoundation                      0x022d5737 __CFRunLoopDoSource1 + 215  
  107. 12  CoreFoundation                      0x022d29c3 __CFRunLoopRun + 979  
  108. 13  CoreFoundation                      0x022d2280 CFRunLoopRunSpecific + 208  
  109. 14  CoreFoundation                      0x022d21a1 CFRunLoopRunInMode + 97  
  110. 15  UIKit                               0x002ba226 -[UIApplication _run] + 625  
  111. 16  UIKit                               0x002c5b58 UIApplicationMain + 1160  
  112. 17  UncaughtE                           0x00002228 main + 102  
  113. 18  UncaughtE                           0x000021b9 start + 53  
  114.   
  115. 不足的地方是,並不是所有的程序崩潰都是由於發生可以捕捉的異常的,有些時候引起崩潰的大多數原因如:內存訪問錯誤,重複釋放等錯誤就無能爲力了,因爲這種錯誤它拋出的是Signal,所以必須要專門做Signal處理。首先定義一個UncaughtExceptionHandler類,.h頭文件的代碼如下:  
  116.   
  117.   
  118. UncaughtExceptionHandler類,.h頭文件的代碼如下:  
  119.   
  120. 1  
  121. 2  
  122. 3  
  123. 4  
  124. 5  
  125. 6  
  126. #import  
  127.    
  128. @interface UncaughtExceptionHandler : NSObject{  
  129. BOOL dismissed;  
  130. }  
  131. @end  
  132. 1  
  133. void InstallUncaughtExceptionHandler();  
  134. 然後在.mm文件實現InstallUncaughtExceptionHandler(),如下:  
  135. void InstallUncaughtExceptionHandler(){  
  136. signal(SIGABRT, MySignalHandler);  
  137. signal(SIGILL, MySignalHandler);  
  138. signal(SIGSEGV, MySignalHandler);  
  139. signal(SIGFPE, MySignalHandler);  
  140. signal(SIGBUS, MySignalHandler);  
  141. signal(SIGPIPE, MySignalHandler);  
  142. }  
  143. 這樣,當應用發生錯誤而產生上述Signal後,就將會進入我們自定義的回調函數MySignalHandler。爲了得到崩潰時的現場信息,還可以加入一些獲取CallTrace及設備信息的代碼,.mm文件的完整代碼如下:  
  144.   
  145. #import "UncaughtExceptionHandler.h"  
  146. #include #include  
  147.    
  148. NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";  
  149.    
  150. NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";  
  151.    
  152. NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";  
  153.    
  154. volatile int32_t UncaughtExceptionCount = 0;  
  155.    
  156. const int32_t UncaughtExceptionMaximum = 10;  
  157.    
  158. const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;  
  159.    
  160. const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;  
  161.    
  162. @implementation UncaughtExceptionHandler  
  163.    
  164. + (NSArray *)backtrace  
  165.    
  166. {  
  167.    
  168.         void* callstack[128];  
  169.    
  170.      int frames = backtrace(callstack, 128);  
  171.    
  172.      charchar **strs = backtrace_symbols(callstack, frames);       
  173.    
  174.      int i;  
  175.    
  176.      NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];  
  177.    
  178.      for (  
  179.    
  180.         i = UncaughtExceptionHandlerSkipAddressCount;  
  181.    
  182.         i < UncaughtExceptionHandlerSkipAddressCount +  
  183.    
  184.             UncaughtExceptionHandlerReportAddressCount;  
  185.    
  186.         i++)  
  187.    
  188.      {  
  189.    
  190.         [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];  
  191.    
  192.      }  
  193.    
  194.      free(strs);      
  195.    
  196.      return backtrace;  
  197.    
  198. }  
  199.    
  200. - (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex  
  201.    
  202. {  
  203.    
  204.     if (anIndex == 0)  
  205.    
  206.     {  
  207.    
  208.         dismissed = YES;  
  209.    
  210.     }  
  211.    
  212. }  
  213.    
  214. - (void)handleException:(NSException *)exception  
  215.    
  216. {  
  217.    
  218.     UIAlertView *alert =  
  219.    
  220.         [[[UIAlertView alloc]  
  221.    
  222.             initWithTitle:NSLocalizedString(@"Unhandled exception", nil)  
  223.    
  224.             message:[NSString stringWithFormat:NSLocalizedString(  
  225.    
  226.                 @"You can try to continue but the application may be unstable.\n"  
  227.    
  228.                 @"%@\n%@", nil),  
  229.    
  230.                 [exception reason],  
  231.    
  232.                 [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]  
  233.    
  234.             delegate:self  
  235.    
  236.             cancelButtonTitle:NSLocalizedString(@"Quit", nil)  
  237.    
  238.             otherButtonTitles:NSLocalizedString(@"Continue", nil), nil nil]  
  239.    
  240.         autorelease];  
  241.    
  242.     [alert show];     
  243.    
  244.     CFRunLoopRef runLoop = CFRunLoopGetCurrent();  
  245.    
  246.     CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);     
  247.    
  248.     while (!dismissed)  
  249.    
  250.     {  
  251.    
  252.         for (NSString *mode in (NSArray *)allModes)  
  253.    
  254.         {  
  255.    
  256.             CFRunLoopRunInMode((CFStringRef)mode, 0.001false);  
  257.    
  258.         }  
  259.    
  260.     }    
  261.    
  262.     CFRelease(allModes);  
  263.    
  264.     NSSetUncaughtExceptionHandler(NULL);  
  265.    
  266.     signal(SIGABRT, SIG_DFL);  
  267.    
  268.     signal(SIGILL, SIG_DFL);  
  269.    
  270.     signal(SIGSEGV, SIG_DFL);  
  271.    
  272.     signal(SIGFPE, SIG_DFL);  
  273.    
  274.     signal(SIGBUS, SIG_DFL);  
  275.    
  276.     signal(SIGPIPE, SIG_DFL);     
  277.    
  278.     if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName])  
  279.    
  280.     {  
  281.  <span style="white-space:pre"> </span>kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);  
  282.     }  
  283.     else  
  284.     {  
  285.         [exception raise];  
  286.     }  
  287. }  
  288. @end  
  289.    
  290. 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)  
  291. {  
  292. return;  
  293. }  
  294.    
  295. NSMutableDictionary *userInfo =  
  296.    
  297. [NSMutableDictionary  
  298.    
  299. dictionaryWithObject:[NSNumber numberWithInt:signal]  
  300.    
  301. forKey:UncaughtExceptionHandlerSignalKey];  
  302.    
  303. NSArray *callStack = [UncaughtExceptionHandler backtrace];  
  304.    
  305. [userInfo  
  306.    
  307. setObject:callStack  
  308.    
  309. forKey:UncaughtExceptionHandlerAddressesKey];  
  310.    
  311. [[[[UncaughtExceptionHandler alloc] init] autorelease]  
  312.    
  313. performSelectorOnMainThread:@selector(handleException:)  
  314.    
  315. withObject:  
  316.    
  317. [NSException  
  318.    
  319. exceptionWithName:UncaughtExceptionHandlerSignalExceptionName  
  320.    
  321. reason:  
  322.    
  323. [NSString stringWithFormat:  
  324.    
  325. NSLocalizedString(@"Signal %d was raised.\n"  
  326.    
  327. @"%@", nil),  
  328.    
  329. signal, getAppInfo()]  
  330.    
  331. userInfo:  
  332.    
  333. [NSDictionary  
  334.    
  335. dictionaryWithObject:[NSNumber numberWithInt:signal]  
  336.    
  337. forKey:UncaughtExceptionHandlerSignalKey]]  
  338.    
  339. waitUntilDone:YES];  
  340.    
  341. }  
  342.    
  343. void InstallUncaughtExceptionHandler()  
  344.    
  345. {  
  346.    
  347. signal(SIGABRT, MySignalHandler);  
  348.    
  349. signal(SIGILL, MySignalHandler);  
  350.    
  351. signal(SIGSEGV, MySignalHandler);  
  352.    
  353. signal(SIGFPE, MySignalHandler);  
  354.    
  355. signal(SIGBUS, MySignalHandler);  
  356.    
  357. signal(SIGPIPE, MySignalHandler);  
  358.    
  359. }  
  360. 在應用自身的 didFinishLaunchingWithOptions 前,加入一個函數:  
  361.   
  362. - (void)installUncaughtExceptionHandler  
  363. {  
  364. InstallUncaughtExceptionHandler();  
  365. }  
  366. 最後,在 didFinishLaunchingWithOptions 中加入這一句代碼就行了:  
  367.   
  368. 1  
  369. [self InstallUncaughtExceptionHandler];  
  370. 現在,基本上所有崩潰都能Hold住了。崩潰時將會顯示出如下的對話框:  
  371.   
  372. iOS開發23:通過歸檔永久存儲數據 - 雙子座的個人頁面 - 開源中國社區  
  373.   
  374. 這樣在崩潰時還能從容地彈出對話框,比起閃退來,用戶也不會覺得那麼不爽。然後在下次啓動時還可以通過郵件來發送Crash文件到郵箱,這就看各個應用的需求了。  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章