开发iOS应用,解决Crash问题始终是一个难题。Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。其实对于未捕获的Objective-C异常,我们是有办法将它记录下来的,如果日志记录得当,能够解决绝大部分崩溃的问题。这里对于UI线程与后台线程分别说明。
一:在.h文件中编写
- @interface UncaughtExceptionHandler : NSObject{
- BOOL dismissed;
- }
- @end
- void HandleException(NSException *exception);
- void SignalHandler(int signal);
- void InstallUncaughtExceptionHandler(void);
二:在.m文件编写
- #import "UncaughtExceptionHandler.h"
- #include <libkern/OSAtomic.h>
- #include <execinfo.h>
- //http://www.cocoachina.com/newbie/tutorial/2012/0829/4672.html
- 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;
- }else if (anIndex==1) {
- NSLog(@"ssssssss");
- }
- }
- - (void)validateAndSaveCriticalApplicationData
- {
- }
- - (void)handleException:(NSException *)exception
- {
- [self validateAndSaveCriticalApplicationData];
- UIAlertView *alert =
- [[[UIAlertView alloc]
- initWithTitle:NSLocalizedString(@"抱歉,程序出现了异常", nil)
- message:[NSString stringWithFormat:NSLocalizedString(
- @"如果点击继续,程序有可能会出现其他的问题,建议您还是点击退出按钮并重新打开\n\n"
- @"异常原因如下:\n%@\n%@", nil),
- [exception reason],
- [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]
- delegate:self
- cancelButtonTitle:NSLocalizedString(@"退出", nil)
- otherButtonTitles:NSLocalizedString(@"继续", 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
- void HandleException(NSException *exception)
- {
- int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
- if (exceptionCount > UncaughtExceptionMaximum)
- {
- return;
- }
- NSArray *callStack = [UncaughtExceptionHandler backtrace];
- NSMutableDictionary *userInfo =
- [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
- [userInfo
- setObject:callStack
- forKey:UncaughtExceptionHandlerAddressesKey];
- [[[[UncaughtExceptionHandler alloc] init] autorelease]
- performSelectorOnMainThread:@selector(handleException:)
- withObject:
- [NSException
- exceptionWithName:[exception name]
- reason:[exception reason]
- userInfo:userInfo]
- waitUntilDone:YES];
- }
- void SignalHandler(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.", nil),
- signal]
- userInfo:
- [NSDictionary
- dictionaryWithObject:[NSNumber numberWithInt:signal]
- forKey:UncaughtExceptionHandlerSignalKey]]
- waitUntilDone:YES];
- }
- void InstallUncaughtExceptionHandler(void)
- {
- NSSetUncaughtExceptionHandler(&HandleException);
- signal(SIGABRT, SignalHandler);
- signal(SIGILL, SignalHandler);
- signal(SIGSEGV, SignalHandler);
- signal(SIGFPE, SignalHandler);
- signal(SIGBUS, SignalHandler);
- signal(SIGPIPE, SignalHandler);
- }
三:最后在AppDelegate中创建
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
- InstallUncaughtExceptionHandler();
- self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
- // Override point for customization after application launch.
- self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease];
- self.window.rootViewController = self.viewController;
- [self.window makeKeyAndVisible];
- return YES;
- }
四:最后的测试
- - (IBAction)onclcko:(id)sender {
- // [alert show];
- NSArray *arry=[NSArray arrayWithObject:@"sss"];
- NSLog(@"%@",[arry objectAtIndex:1]);
- }
五:结果
其他
iOS已发布应用中对异常信息捕获和处理
iOS开发中我们会遇到程序抛出异常退出的情况,如果是在调试的过程中,异常的信息是一目了然,但是如果是在已经发布的程序中,获取异常的信息有时候是比较困难的。
iOS提供了异常发生的处理API,我们在程序启动的时候可以添加这样的Handler,这样的程序发生异常的时候就可以对这一部分的信息进行必要的处理,适时的反馈给开发者。
不足的地方是,并不是所有的程序崩溃都是由于发生可以捕捉的异常的,有些时候是因为内存等一些其他的错误导致程序的崩溃,这样的信息是不在这里体现的。
我做了一个简单的类,进行很基本的操作,可以添加和获取Handler,捕获到异常后将信息写入到app的Documens下的Exception.txt中。
其实还有很多的处理的办法。
l 比如可以在程序下一次起来的时候读取这个异常文件发生到服务端。
l 或者直接就是在处理代码中用openurl的方式(mailto:)调用发送邮件的方式,将异常信息直接变成邮件发送到指定地址。
以下是完整的代码实现。
使用场景示例:
#pragma mark - #pragma mark Application lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[window makeKeyAndVisible]; [NdUncaughtExceptionHandler setDefaultHandler]; NSArray *array = [NSArray arrayWithObject:@"there is only one objective in this arary,call index one, app will crash and throw an exception!"]; NSLog(@"%@", [array objectAtIndex:1]);
return YES; } |
基本接口展示:
#import <Foundation/Foundation.h>
@interface NdUncaughtExceptionHandler : NSObject {
+ (void)setDefaultHandler; + (NSUncaughtExceptionHandler*)getHandler;
@end //还可以选择设置自定义的handler,让用户取选择 |
接口实现展示
#import "NdUncaughtExceptionHandler.h"
NSString *applicationDocumentsDirectory() { return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; }
void UncaughtExceptionHandler(NSException *exception) { NSArray *arr = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name];
NSString *url = [NSString stringWithFormat:@"=============异常崩溃报告=============\nname:\n%@\nreason:\n%@\ncallStackSymbols:\n%@", name,reason,[arr componentsJoinedByString:@"\n"]]; NSString *path = [applicationDocumentsDirectory() stringByAppendingPathComponent:@"Exception.txt"]; [url writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; //除了可以选择写到应用下的某个文件,通过后续处理将信息发送到服务器等 //还可以选择调用发送邮件的的程序,发送信息到指定的邮件地址 //或者调用某个处理程序来处理这个信息 }
@implementation NdUncaughtExceptionHandler
-(NSString *)applicationDocumentsDirectory { return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; }
+ (void)setDefaultHandler { NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler); }
+ (NSUncaughtExceptionHandler*)getHandler { return NSGetUncaughtExceptionHandler(); }
@end |
异常崩溃报告:
=============异常崩溃报告============= name: NSRangeException reason: *** -[NSArray objectAtIndex:]: index 1 beyond bounds [0 .. 0] callStackSymbols: 0 CoreFoundation 0x02393919 __exceptionPreprocess + 185 1 libobjc.A.dylib 0x024e15de objc_exception_throw + 47 2 CoreFoundation 0x0238958c -[__NSArrayI objectAtIndex:] + 236 3 UncaughtE 0x000022e8 -[UncaughtEAppDelegate application:didFinishLaunchingWithOptions:] + 157 4 UIKit 0x002b8543 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163 5 UIKit 0x002ba9a1 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 346 6 UIKit 0x002c4452 -[UIApplication handleEvent:withNewEvent:] + 1958 7 UIKit 0x002bd074 -[UIApplication sendEvent:] + 71 8 UIKit 0x002c1ac4 _UIApplicationHandleEvent + 7495 9 GraphicsServices 0x02bf9afa PurpleEventCallback + 1578 10 CoreFoundation 0x02374dc4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52 11 CoreFoundation 0x022d5737 __CFRunLoopDoSource1 + 215 12 CoreFoundation 0x022d29c3 __CFRunLoopRun + 979 13 CoreFoundation 0x022d2280 CFRunLoopRunSpecific + 208 14 CoreFoundation 0x022d21a1 CFRunLoopRunInMode + 97 15 UIKit 0x002ba226 -[UIApplication _run] + 625 16 UIKit 0x002c5b58 UIApplicationMain + 1160 17 UncaughtE 0x00002228 main + 102 18 UncaughtE 0x000021b9 start + 53 |