app上线后在某些情况下肯定会有崩溃现象发生
获取崩溃信息的方式有很多,比较常见的是使用友盟统计、腾讯Bugly等.但是需要集成第三方框架,并且信息传到第三方服务器上,如果公司很反感第三方框架,或者很注重数据安全和隐私,或者是资金问题,是不会采用这种方式的.
还有就是我目前这个项目使用的:使用原生框架应用内崩溃收集,并上传服务器
崩溃信息处理主要分两步:
1.在用户app抛出异常的时候,我们捕获到异常并处理(例如搞个弹窗)
2.将异常写入沙盒中或上传到服务器
如何捕获异常
iOS 原生的类NSException可以用来做异常处理,但功能有限,而引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了
因为这种错误它抛出的是signal,所以还要做signal处理.
我目前的项目中做了两种处理.下面代码注释很详细.
(关于NSException和signal详细的请阅读文末的参考链接,前辈们写得很好我就不重复写了)
// 将获取崩溃信息函数写在AppDelegate这个方法中.以保证在程序开始运行就具有获取崩溃信息的功能
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//code...
//调用自定义类中的收集崩溃信息的方法
[CJAppUncaughtExceptionHandler InstallUncaughtExceptionHandler];
//code...
return YES;
}
下面是在自定义类的.m文件中的代码,.h中就一个方法声明,下面有注释
#include
#include
//未捕获的异常处理器信号异常的名字
NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
//异常处理程序信号保存到字典的key
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;
#import "CJAppUncaughtExceptionHandler.h"
void InstallUncaughtExceptionHandler();
@implementation CJAppUncaughtExceptionHandler
NSUncaughtExceptionHandler* _uncaughtExceptionHandler = nil;
//暴露在.h中,在AppDelegate中调用,保证在程序开始运行就具有获取崩溃信息的功能
+ (void)InstallUncaughtExceptionHandler
{
InstallUncaughtExceptionHandler();
}
//创建异常对象调用
+ (NSString* )getAppInfo
{
NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\n",
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
[UIDevice currentDevice].model,
[UIDevice currentDevice].systemName,
[UIDevice currentDevice].systemVersion
];
return appInfo;
}
//ExceptionSignalHandler()会调用
+ (NSArray *)backtrace
{
//定义一个指针数组
void* callstack[128];
//该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在callstack中。
//参数128用来指定callstack中可以保存多少个void* 元素。
//函数返回值是实际获取的指针个数,最大不超过128大小在callstack中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。
int frames = backtrace(callstack, 128);
//backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组.
//参数callstack应该是从backtrace函数获取的数组指针,frames是该数组中的元素个数(backtrace的返回值)
//函数返回值是一个指向字符串数组的指针,它的大小同callstack相同.每个字符串包含了一个相对于callstack中对应元素的可打印信息.
char **strs = backtrace_symbols(callstack, frames);
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (int i=0;i
//捕获信号后的回调函数,由InstallUncaughtExceptionHandler()调用
void ExceptionSignalHandler(int signalval)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
//如果太多不用处理
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signalval]
forKey:UncaughtExceptionHandlerSignalKey];
//此方法获取的栈信息太少
NSArray *callStack = [CJAppUncaughtExceptionHandler backtrace];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
//创建一个oc异常对象
NSString *appInfo = [CJAppUncaughtExceptionHandler getAppInfo];
NSString *exceptionReason = [NSString stringWithFormat:@"Signal %d was raised.\n"@"%@", signalval, appInfo];
NSException *exception = [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
reason:exceptionReason
userInfo:userInfo];
//在沙盒的Documents目录下创建文件保存//也可以上传到服务器或者发送邮件之类的
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
exception,@"exception",
userInfo,@"stack_info", nil];
NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *path=[paths objectAtIndex:0];
NSString *fileName = [path stringByAppendingPathComponent:@"crash.plist"];
[dic writeToFile:fileName atomically:YES];
//取消异常捕获,在应用退出或者销毁的时候设置,只需调用此函数,参数为NULL
NSSetUncaughtExceptionHandler(NULL);
//signal函数中的信号处理函数handler,可以是用户指定的一个信号处理函数,也可以是内核特定的函数指针SIG_IGN或SIG_DFL。
//若信号句柄是SIG_IGN或SIG_DFL,则分别表示对捕获的信号采取忽略操作或者默认操作。
//其实对于大多数信号的系统默认动作是终止该进程。这与不写此处理函数是一样的。
signal(SIGHUP, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGQUIT,SIG_DFL);
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()可以用来送参数sig 指定的信号给参数pid 指定的进程。
//getpid()用来取得目前进程的进程识别码
//第二个参数sig即SIGHUP之类的信号
kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
}
else
{
//抛出异常,不做处理
[exception raise];
}
}
//注册崩溃拦截
void InstallUncaughtExceptionHandler()
{
[CJAppUncaughtExceptionHandler InstallUncaughtExceptionHandlerB];
//signal信号函数,第一个参数表示需要处理的信号值,第二个参数为处理函数
//由挂起导致的终止信号
signal(SIGHUP, ExceptionSignalHandler);
//由中断导致的终止信号
signal(SIGINT, ExceptionSignalHandler);
//由退出导致的终止信号
signal(SIGQUIT, ExceptionSignalHandler);
//注册程序由于abort()函数调用发生的程序终止信号
signal(SIGABRT, ExceptionSignalHandler);
//注册程序由于非法指令产生的程序终止信号
signal(SIGILL, ExceptionSignalHandler);
//注册程序由于无效内存的引用导致的程序终止信号
signal(SIGSEGV, ExceptionSignalHandler);
//注册程序由于浮点数异常导致的程序终止信号
signal(SIGFPE, ExceptionSignalHandler);
//注册程序由于内存地址未对齐导致的程序终止信号
signal(SIGBUS, ExceptionSignalHandler);
//程序通过端口发送消息失败导致的程序终止信号
signal(SIGPIPE, ExceptionSignalHandler);
}
同时还要注意不要用try-catch语句捕捉NSException异常,
因为在Cocoa Touch 框架中,引发自己的异常然后再用try-catch语句块来捕捉,这非常消耗资源,
但是捕捉系统引发的异常就不会
参考相关链接:
1.http://blog.csdn.net/yhhwatl/article/details/34432603?utm_source=tuicool&utm_medium=referral
2.http://www.jianshu.com/p/77660e626874#
3.http://www.cnblogs.com/daxiaxiaohao/p/4466097.html?utm_source=tuicool&utm_medium=referral
4.http://www.cnblogs.com/mickole/p/3246702.html
5.http://blog.sina.com.cn/s/blog_8184e03301013ddz.html
6.http://baike.baidu.com/link?url=RT6L_RWOcmCOICq3BuEcdTQfRlqgsaNmLUnGn2QrGBtIkldsqhMBmOgSm-CQvcH-h3uqupCf8aS98Xl3Bm1Ctq
7.https://nianxi.net/ios/ios-crash-reporter.html