6.1iOS异常捕捉,日志组件

来源:互联网 发布:哪里能买到呼死你软件 编辑:程序博客网 时间:2024/06/16 21:45

在开发APP时,我们通常都会需要捕获异常,防止应用程序突然的崩溃,防止给予用户不友好的体验。

Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。其实对于未捕获的Objective-C异常,我们是有办法将它记录下来的,如果日志记录得当,能够解决绝大部分崩溃的问题。这里对于UI线程与后台线程分别说明。


1、首先需要在appDelegate中使用InstallUncaughtExceptionHandler()用于监听

- (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;}



2、添加UncaughtExceptionHandler这个类iOS SDK提供的函数是NSSetUncaughtExceptionHandler来进行异常处理。但是无法处理内存访问错误、重复释放等错误,因为这些错误发送的SIGNAL。所以需要处理这些SIGNAL
#import <UIKit/UIKit.h>@interface UncaughtExceptionHandler : NSObject{BOOL dismissed;}@endvoid HandleException(NSException *exception);void SignalHandler(int signal);void InstallUncaughtExceptionHandler(void);


#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)validateAndSaveCriticalApplicationData:(NSException *)exception{    /**     *  获取异常崩溃信息     */    NSArray *callStack = [exception callStackSymbols];    NSString *reason = [exception reason];    NSString *name = [exception name];    NSString *content = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[callStack componentsJoinedByString:@"\n"]];        /**     *  把异常崩溃信息发送至开发者邮件     */    NSMutableString *mailUrl = [NSMutableString string];    [mailUrl appendString:@"mailto:test@qq.com"];    [mailUrl appendString:@"?subject=程序异常崩溃,请配合发送异常报告,谢谢合作!"];    [mailUrl appendFormat:@"&body=%@", content];    // 打开地址    NSString *mailPath = [mailUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]];}- (void)handleException:(NSException *)exception{    [self validateAndSaveCriticalApplicationData: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"@"Debug details follow:\n%@\n%@", nil),[exception reason],[[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]delegate:selfcancelButtonTitle: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];}}@endvoid HandleException(NSException *exception){int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);if (exceptionCount > UncaughtExceptionMaximum){return;}NSArray *callStack = [UncaughtExceptionHandler backtrace];NSMutableDictionary *userInfo =[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];[userInfosetObject:callStackforKey:UncaughtExceptionHandlerAddressesKey];[[[[UncaughtExceptionHandler alloc] init] autorelease]performSelectorOnMainThread:@selector(handleException:)withObject:[NSExceptionexceptionWithName:[exception name]reason:[exception reason]userInfo:userInfo]waitUntilDone:YES];}void SignalHandler(int signal){int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);if (exceptionCount > UncaughtExceptionMaximum){return;}NSMutableDictionary *userInfo =[NSMutableDictionarydictionaryWithObject:[NSNumber numberWithInt:signal]forKey:UncaughtExceptionHandlerSignalKey];NSArray *callStack = [UncaughtExceptionHandler backtrace];[userInfosetObject:callStackforKey:UncaughtExceptionHandlerAddressesKey];[[[[UncaughtExceptionHandler alloc] init] autorelease]performSelectorOnMainThread:@selector(handleException:)withObject:[NSExceptionexceptionWithName:UncaughtExceptionHandlerSignalExceptionNamereason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]userInfo:[NSDictionarydictionaryWithObject:[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);}


3 部分情况的崩溃我们是无法避免的,就算是QQ也会有崩溃的时候。因此我们可以在程序崩溃之前做一些“动作”(收集错误信息),以下例子是把捕获到的异常发送至开发者的邮箱。主要体现在以下代码

- (void)validateAndSaveCriticalApplicationData:(NSException *)exception{    /**     *  获取异常崩溃信息     */    NSArray *callStack = [exception callStackSymbols];    NSString *reason = [exception reason];    NSString *name = [exception name];    NSString *content = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[callStack componentsJoinedByString:@"\n"]];        /**     *  把异常崩溃信息发送至开发者邮件     */    NSMutableString *mailUrl = [NSMutableString string];    [mailUrl appendString:@"mailto:test@qq.com"];    [mailUrl appendString:@"?subject=程序异常崩溃,请配合发送异常报告,谢谢合作!"];    [mailUrl appendFormat:@"&body=%@", content];    // 打开地址    NSString *mailPath = [mailUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]];}- (void)handleException:(NSException *)exception{    [self validateAndSaveCriticalApplicationData: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"@"Debug details follow:\n%@\n%@", nil),[exception reason],[[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]delegate:selfcancelButtonTitle: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];}}


注意:如果在模拟器中运行会产生错误

LaunchServices: ERROR: There is no registered handler for URL scheme mailto

Launch Services是一个应用打开另外应用的服务框架,出现这个错误的原因是,设备或者模拟器没有安装打开该服务的程序。上面的错误是在模拟器中没有打开邮件的设备。


0 0
原创粉丝点击