线上APP获取崩溃信息的方式:
- 使用系统 API 收集崩溃信息并上传到服务器。
- 使用友盟、Bugly 等第三方收集分析SDK。
- 使用 iTunes Connect 上的崩溃收集服务。
这里暂时只讲第一种。iOS提供了异常发生时的处理API,在程序启动的时候可以添加这样的Handler,这样程序发生异常的时候就可以对异常进行必要的处理。
1
| void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);
|
下面的类就利用了系统提供的API来捕获出现的崩溃,并将异常信息存储到固定目录下的文件中,
1 2 3 4 5 6 7 8
| #import <Foundation/Foundation.h>
@interface ExceptionHandler : NSObject
+ (void)setDefaultHandler;
@end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #import "ExceptionHandler.h"
volatile int32_t UncaughtExceptionCount = 0; volatile int32_t UncaughtExceptionMaximum = 10;
#pragma mark -文件目录 NSString *exceptionFilePath() { return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) lastObject]; }
#pragma mark -收到异常通知时的回调函数 void UncaughtExceptionHandler(NSException *exception) { NSArray *arr = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name];
NSString *info = [NSString stringWithFormat:@"+异常崩溃报告+\nname:\n%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[arr componentsJoinedByString:@"\n"]];
NSString *path = [exceptionFilePath() stringByAppendingPathComponent:@"Exception.txt"];
[info writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; }
@implementation ExceptionHandler
#pragma mark -开始监听异常 + (void)setDefaultHandler { NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler); }
|
调用代码:
1 2 3 4 5 6 7 8 9
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [ExceptionHandler setDefaultHandler]; NSArray *anArr = @[@(0)]; anArr[5];
return YES; }
|
崩溃后得到日志信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| +异常崩溃报告+ name: NSRangeException reason: *** -[__NSSingleObjectArrayI objectAtIndex:]: index 5 beyond bounds [0 .. 0] callStackSymbols: 0 CoreFoundation 0x00000001070dd1ab __exceptionPreprocess + 171 1 libobjc.A.dylib 0x0000000106772f41 objc_exception_throw + 48 2 CoreFoundation 0x000000010711d2df -[__NSSingleObjectArrayI objectAtIndex:] + 111 3 ASDF 0x0000000105e5a8b2 -[AppDelegate application:didFinishLaunchingWithOptions:] + 242 ... 28 CoreFoundation 0x0000000107064b49 __CFRunLoopDoSources0 + 185 29 CoreFoundation 0x000000010706412f __CFRunLoopRun + 1279 30 CoreFoundation 0x00000001070639b9 CFRunLoopRunSpecific + 409 31 GraphicsServices 0x000000010c0ad9c6 GSEventRunModal + 62 32 UIKit 0x00000001075585e8 UIApplicationMain + 159 33 ASDF 0x0000000105e5bf4f main + 111 34 libdyld.dylib 0x000000010aa71d81 start + 1
|
但是,并不是所有的程序崩溃都是由于发生可以捕捉的异常的,有些时候引起崩溃的原因可能是:内存访问错误、重复释放等。上面的API对这些错误就无能为力了,因为这种错误它抛出的是Signal,所以必须要专门做Signal处理。
此时,可以使用下面的函数来注册signal异常时的回调函数:
1
| signal(SIGABRT, signalExceptionHandler);
|
这样,当应用发生错误而产生上述signal后,就会进入我们自定义的回调函数signalExceptionHandler中。为了得到崩溃时的现场信息,还可以加入一些获取CallTrace的方法,完整代码如下:
1 2 3 4 5 6 7 8 9
| #import <Foundation/Foundation.h>
@interface ExceptionHandler : NSObject
+ (instancetype)shareInstance; + (NSArray *)backtrace; + (void)installExceptionHandler;
@end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| #import "ExceptionHandler.h" #import <UIKit/UIKit.h> #include <libkern/OSAtomic.h> #include <execinfo.h> #include <sys/signal.h>
volatile int32_t UncaughtExceptionCount = 0;
volatile int32_t UncaughtExceptionMaximum = 10;
const NSInteger ExceptionHandlerSkipAddressCount = 5; const NSInteger ExceptionHandlerReportAddressCount = 10;
static ExceptionHandler *mExceptionHandler = nil;
#pragma mark -捕获信号后的回调函数 void signalExceptionHandler(int signo) { int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount); if (exceptionCount > UncaughtExceptionMaximum){ return; } NSArray *callStack = [ExceptionHandler backtrace];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject: [NSNumber numberWithInt:signo] forKey:@"signal"]; [userInfo setValue:callStack forKey:@"callStack"];
NSException *ex = [NSException exceptionWithName:@"Name" reason:nil userInfo:userInfo];
[[ExceptionHandler shareInstance] performSelectorOnMainThread:@selector(onHandleSignalException:) withObject:ex waitUntilDone:YES]; }
@implementation ExceptionHandler
+ (instancetype)shareInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (mExceptionHandler == nil) { mExceptionHandler = [[ExceptionHandler alloc] init]; } }); return mExceptionHandler; }
#pragma mark -注册异常处理回调 + (void)installExceptionHandler {
signal(SIGABRT, signalExceptionHandler);
signal(SIGILL, signalExceptionHandler);
signal(SIGSEGV, signalExceptionHandler);
signal(SIGFPE, signalExceptionHandler);
signal(SIGBUS, signalExceptionHandler);
signal(SIGPIPE, signalExceptionHandler); }
#pragma mark -处理异常用到的方法 - (void)onHandleSignalException:(NSException *)exception { NSLog(@"++++出现崩溃:\n%@",exception.userInfo);
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); }
#pragma mark -获取调用堆栈 + (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 = ExceptionHandlerSkipAddressCount; i < ExceptionHandlerSkipAddressCount + ExceptionHandlerReportAddressCount; i++) { [backtrace addObject:[NSString stringWithUTF8String:strs[i]]]; } free(strs); return backtrace; } @end
|
调用示例:
1 2 3 4 5 6
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [ExceptionHandler installExceptionHandler]; return YES; }
|
这样,基本上所有崩溃都能捕获了。