在APP发布到线上后,会出现用户使用时闪退的糟糕情况。为了改进用户体验,就需要收集app崩溃的日志信息,来完善应用。
像数组越界、字典操作对象值为nil等都会发出一般异常,利用 NSSetUncaughtExceptionHandler就能捕获。但是像访问错误内存块、引用野指针等抛出的是Signal,需要做Signal处理。
下面是捕获处理这两种异常的代码:
首先,在application:(UIApplication *)application didFinishLaunchingWithOptions:方法里添加一个异常捕获的入口函数:RegisterExceptionHandler();
下面是自建CatchCrash类:
#import <Foundation/Foundation.h>
@interface CatchCrash : NSObject
//注册异常捕获
void RegisterExceptionHandler(void);
@end
。m里:
#import "CatchCrash.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>
#define CRASHREASON @"crashReason"
volatile int32_t exceptionNumber = 0;
const int32_t exceptionMaximum = 10;
@implementation CatchCrash
//获取堆栈信息
+ (NSArray *)backtrace{
void* callstack[128];
int frames = backtrace(callstack, 128);
char **strs = backtrace_symbols(callstack, frames);
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (int i = 0; i < frames; i ++) {
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
#pragma mark 异常处理
- (void)handleException:(NSException *)exception{
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);
//捕获到异常后处理
NSArray *callStack = [[exception userInfo]objectForKey:@"UncaughtExceptionHandlerAddressesKey"];
NSString *reason = [exception reason];
NSString *name = [exception name];
//错误信息
NSString *crashContent = [NSString stringWithFormat:@"crashName:%@ AND crashReason:%@ AND callStackSymbols:%@",name,reason,[callStack componentsJoinedByString:@"\n"]];
/*
//IMEI
//手机型号
//报错日期
//系统版本
*/
//有效信息自行拼接
NSString *message = [NSString stringWithFormat:@"%@", crashContent];
NSLog(@"*****崩溃信息log : \n%@\n",message);
//本地存储
NSUserDefaults *crashUser = [NSUserDefaults standardUserDefaults];
NSString *crashPreviousContent = [crashUser objectForKey:CRASHREASON];
if (crashPreviousContent && crashPreviousContent.length > 0) {
[crashUser setObject:[NSString stringWithFormat:@"%@||%@", crashPreviousContent, message] forKey:CRASHREASON];
} else {
[crashUser setObject:message forKey:CRASHREASON];
}
[crashUser synchronize];
if ([[exception name] isEqual:@"UncaughtExceptionHandlerSignalExceptionName"])
{
kill(getpid(), [[[exception userInfo] objectForKey:@"UncaughtExceptionHandlerSignalKey"] intValue]);
}
else
{
[exception raise];
}
}
@end
#pragma mark 注册异常处理
void RegisterExceptionHandler(void)
{
NSSetUncaughtExceptionHandler(&handleException);
signal(SIGABRT, signalHandler);
signal(SIGILL, signalHandler);
signal(SIGSEGV, signalHandler);
signal(SIGFPE, signalHandler);
signal(SIGBUS, signalHandler);
signal(SIGPIPE, signalHandler);
}
#pragma mark 普通异常处理
//处理exception报错
void handleException(NSException *exception)
{
int32_t exceptionCount = OSAtomicIncrement32(&exceptionNumber);
if (exceptionCount > exceptionMaximum)
{
return;
}
NSArray *callStack = [CatchCrash backtrace];
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:@"UncaughtExceptionHandlerAddressesKey"];
[[[CatchCrash alloc] init]
performSelectorOnMainThread:@selector(handleException:)
withObject:[NSException exceptionWithName:[exception name]reason:[exception reason]userInfo:userInfo]waitUntilDone:YES];
}
#pragma mark 异常信号处理
//处理signal报错
void signalHandler(int signal)
{
int32_t exceptionCount = OSAtomicIncrement32(&exceptionNumber);
if (exceptionCount > exceptionMaximum)
{
return;
}
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal]forKey:@"UncaughtExceptionHandlerSignalKey"];
NSArray *callStack = [CatchCrash backtrace];
[userInfo setObject:callStack forKey:@"UncaughtExceptionHandlerAddressesKey"];
[[[CatchCrash alloc] init]
performSelectorOnMainThread:@selector(handleException:)
withObject:[NSException exceptionWithName:@"UncaughtExceptionHandlerSignalExceptionName"
reason:[NSString stringWithFormat:@"Signal %d was raised.",
signal]userInfo:userInfo]waitUntilDone:YES];
}
如果有需要,还可以在以上的代码基础上进行拓展,比如可以把存在本地的crash日志,在APP下次启动时发送给服务器,供开发者参考。