如何讓IOS應(yīng)用從容地崩潰雖然大家都不愿意看到程序崩潰,但可能崩潰是每個(gè)應(yīng)用必須面對(duì)的現(xiàn)實(shí),既然崩潰已經(jīng)發(fā)生,,無(wú)法阻擋了,,那我們就讓它崩也崩得淡定點(diǎn)吧。文/donglin 注:鑒于多名網(wǎng)友對(duì)文中代碼提出的質(zhì)疑,,小編聯(lián)系了作者,,迅速給予更正并更新。感謝大家的監(jiān)督與支持,! 雖然大家都不愿意看到程序崩潰,,但可能崩潰是每個(gè)應(yīng)用必須面對(duì)的現(xiàn)實(shí),既然崩潰已經(jīng)發(fā)生,,無(wú)法阻擋了,,那我們就讓它崩也崩得淡定點(diǎn)吧。 IOS SDK中提供了一個(gè)現(xiàn)成的函數(shù) NSSetUncaughtExceptionHandler 用來(lái)做異常處理,,但功能非常有限,,而引起崩潰的大多數(shù)原因如:內(nèi)存訪問(wèn)錯(cuò)誤,重復(fù)釋放等錯(cuò)誤就無(wú)能為力了,,因?yàn)檫@種錯(cuò)誤它拋出的是Signal,,所以必須要專門(mén)做Signal處理。首先定義一個(gè)UncaughtExceptionHandler類,,.h頭文件的代碼如下: #import <UIKit/UIKit.h> @interface UncaughtExceptionHandler : NSObject { BOOL dismissed; } @end void InstallUncaughtExceptionHandler();
然后在.mm文件實(shí)現(xiàn)InstallUncaughtExceptionHandler(),,如下: void InstallUncaughtExceptionHandler() { signal(SIGABRT, MySignalHandler); signal(SIGILL, MySignalHandler); signal(SIGSEGV, MySignalHandler); signal(SIGFPE, MySignalHandler); signal(SIGBUS, MySignalHandler); signal(SIGPIPE, MySignalHandler); }
這樣,當(dāng)應(yīng)用發(fā)生錯(cuò)誤而產(chǎn)生上述Signal后,,就將會(huì)進(jìn)入我們自定義的回調(diào)函數(shù)MySignalHandler,。為了得到崩潰時(shí)的現(xiàn)場(chǎng)信息,還可以加入一些獲取CallTrace及設(shè)備信息的代碼,,.mm文件的完整代碼如下: #import "UncaughtExceptionHandler.h" #include <libkern/OSAtomic.h> #include <execinfo.h> NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName"; NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey"; NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey"; volatile int32_t UncaughtExceptionCount = 0; const int32_t UncaughtExceptionMaximum = 10; const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4; const NSInteger UncaughtExceptionHandlerReportAddressCount = 5; @implementation UncaughtExceptionHandler + (NSArray *)backtrace { void* callstack[128]; int frames = backtrace(callstack, 128); char **strs = backtrace_symbols(callstack, frames); int i; NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; for ( i = UncaughtExceptionHandlerSkipAddressCount; i < UncaughtExceptionHandlerSkipAddressCount + UncaughtExceptionHandlerReportAddressCount; i++) { [backtrace addObject:[NSString stringWithUTF8String:strs[i]]]; } free(strs); return backtrace; } - (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex { if (anIndex == 0) { dismissed = YES; } } - (void)handleException:(NSException *)exception { UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Unhandled exception", nil) message:[NSString stringWithFormat:NSLocalizedString( @"You can try to continue but the application may be unstable.\n" @"%@\n%@", nil), [exception reason], [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]] delegate:self cancelButtonTitle:NSLocalizedString(@"Quit", nil) otherButtonTitles:NSLocalizedString(@"Continue", nil), nil] autorelease]; [alert show]; CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop); while (!dismissed) { for (NSString *mode in (NSArray *)allModes) { CFRunLoopRunInMode((CFStringRef)mode, 0.001, false); } } CFRelease(allModes); NSSetUncaughtExceptionHandler(NULL); signal(SIGABRT, SIG_DFL); signal(SIGILL, SIG_DFL); signal(SIGSEGV, SIG_DFL); signal(SIGFPE, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGPIPE, SIG_DFL); if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName]) { kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]); } else { [exception raise]; } } @end 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) { return; }
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey]; NSArray *callStack = [UncaughtExceptionHandler backtrace]; [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey]; [[[[UncaughtExceptionHandler alloc] init] autorelease] performSelectorOnMainThread:@selector(handleException:) withObject: [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason: [NSString stringWithFormat: NSLocalizedString(@"Signal %d was raised.\n" @"%@", nil), signal, getAppInfo()] userInfo: [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey]] waitUntilDone:YES]; } void InstallUncaughtExceptionHandler() { signal(SIGABRT, MySignalHandler); signal(SIGILL, MySignalHandler); signal(SIGSEGV, MySignalHandler); signal(SIGFPE, MySignalHandler); signal(SIGBUS, MySignalHandler); signal(SIGPIPE, MySignalHandler); }
在應(yīng)用自身的 didFinishLaunchingWithOptions 前,,加入一個(gè)函數(shù): - (void)installUncaughtExceptionHandler { InstallUncaughtExceptionHandler(); }
最后,在 didFinishLaunchingWithOptions 中加入這一句代碼就行了: [self InstallUncaughtExceptionHandler]; 現(xiàn)在,,基本上所有崩潰都能Hold住了。崩潰時(shí)將會(huì)顯示出如下的對(duì)話框:
這樣在崩潰時(shí)還能從容地彈出對(duì)話框,,比起閃退來(lái),,用戶也不會(huì)覺(jué)得那么不爽。然后在下次啟動(dòng)時(shí)還可以通過(guò)郵件來(lái)發(fā)送Crash文件到郵箱,,這就看各個(gè)應(yīng)用的需求了,。
原文來(lái)自:觸控科技博客 |
|
來(lái)自: quasiceo > 《計(jì)算機(jī)》