2016年12月6日
一.效果图(奔溃后第二次启动后的打开隐藏功能显示奔溃信息)
二.实现
1.注册异常捕获机制(程序启动接口)
2.单例里面实现异常捕获相关内容
基本思想:注册捕获系统异常和signal异常的C语言函数,捕获后组装含堆栈信息的exception,统一交给oc的handleException处理。
将异常信息保存在沙盒 LastExceptionLog.txt文件里,程序下次启动就可以打印出来(不建议,在第一次奔溃处就弹出异常提示框,奔溃状态不一定准确)
// Singleton.h
#define singleton_interface(className) \
+ (className *)shared##className;
// @implementation
#define singleton_implementation(className) \
static className *_instance; \
+ (id)allocWithZone:(NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
+ (className *)shared##className \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
}
// HuConfigration.h
@interface HuConfigration : NSObject
{
}
singleton_interface(HuConfigration)
/**
* @brief 注册\捕获系统异常
*/
- (void)installExceptionHandler;
@end
// HuConfigration.m
#import "HuConfigration.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>
@implementation HuConfigration
singleton_implementation(HuConfigration)
/异常捕获相关代码
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;
//系统异常捕获
void huSystemExceptionHandler(NSException *exception)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSArray *callStack = [exception callStackSymbols];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
NSException *tmpException = [NSException
exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo];
HuConfigration *config = [HuConfigration sharedHuConfigration];
[config performSelectorOnMainThread:@selector(handleException:) withObject:tmpException waitUntilDone:YES];
//避免异常日志没写完,程序就中断
[config blockSystemExceptionEvent];
}
//信号异常捕获
void huSignalHandler(int signal)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal]
forKey:UncaughtExceptionHandlerSignalKey];
NSArray *callStack = [HuConfigration backtrace]; //获取signal类型的堆栈信息
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
NSString *reason = [NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal];
NSException *exception = [NSException
exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
reason:reason
userInfo:userInfo];
HuConfigration *config = [HuConfigration sharedHuConfigration];
[config performSelectorOnMainThread:@selector(handleException:) withObject:exception waitUntilDone:YES];
//避免异常日志没写完,程序就中断
[config blockSystemExceptionEvent];
}
/**
保存最新的日志文件到沙盒目录
*/
void saveLastException(NSArray *callback)
{
NSArray *arr = callback;//得到当前调用栈信息
NSString *fileContentStr = [NSString stringWithFormat:@"%@",arr];
NSError *error;
if ([fileContentStr writeToFile: [HuConfigration dataFilePathWithFileName:@"LastExceptionLog.txt" WithDirType:NSDocumentDirectory] atomically:YES encoding:NSUTF8StringEncoding error:&error]) {
NSLog(@"save success");
}
}
//注册捕获系统异常
- (void)installExceptionHandler
{
//1.系统异常
NSSetUncaughtExceptionHandler(&huSystemExceptionHandler);
//2.系统信号量中断异常
signal(SIGABRT, huSignalHandler);
signal(SIGILL, huSignalHandler);
signal(SIGSEGV, huSignalHandler);
signal(SIGFPE, huSignalHandler);
signal(SIGBUS, huSignalHandler);
signal(SIGPIPE, huSignalHandler);
}
- (void)handleException:(NSException *)exception
{
NSDictionary *nameDict = @{
@"NSGenericException":@"通用异常",
@"NSRangeException":@"范围异常",
@"NSInvalidArgumentException":@"非法参数异常",
@"NSInternalInconsistencyException":@"内部矛盾异常",
@"NSMallocException":@"内存分配异常",
@"NSObjectInaccessibleException":@"对象访问异常",
@"NSObjectNotAvailableException":@"对象不可用异常",
@"NSDestinationInvalidException":@"目标地址无效异常",
@"NSPortTimeoutException":@"端口超时异常",
@"NSInvalidSendPortException":@"无效的发送端口异常",
@"NSInvalidReceivePortException":@"无效的端口异常",
@"NSPortSendException":@"端口发送异常",
@"NSPortReceiveException":@"端口接收异常",
@"NSOldStyleException":@"旧风格异常"
};
NSString *valueName = @"";
NSString *keyName = exception.name;
if ([nameDict objectForKey:keyName]) {
valueName = nameDict[keyName];
}else{
valueName = [keyName hasPrefix:@"Signal"] ? @"信号之类异常" : @"其他异常";
}
NSDictionary *dict = @{@"time":[HuAlilog getDateStr], @"name":valueName, @"reason":[exception reason]};
//保存数据用于上传阿里云日志服务用,上传完就移除
[kUserDefaults setObject:dict forKey:@"exception"];
[kUserDefaults synchronize];
//保存最新的日志文件到沙盒目录
NSArray *callStack = [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey];
saveLastException(callStack);
_dismissed = YES; //已经写完了
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];
}
}
//防止日志没有写完就奔溃
- (void) blockSystemExceptionEvent
{
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (![HuConfigration sharedHuConfigration].dismissed)
{
for (NSString *mode in (__bridge NSArray *)allModes)
{
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
// _dismissed = NO;//不写也没事奔溃后肯定要重新运行
CFRelease(allModes);
}
+ (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;
}
//根据传入文件名,返回关键目录根目录加上文件的绝对路径 document
+ (NSString *)dataFilePathWithFileName:(NSString *)fileName WithDirType:(NSSearchPathDirectory)dirType
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(dirType, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *absolutefileName = [fileName lastPathComponent];
return [documentsDirectory stringByAppendingPathComponent:absolutefileName];
}
@end
如果您发现本文对你有所帮助,如果您认为其他人也可能受益,请把它分享出去。