iOS获取App崩溃信息

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

你可能感兴趣的:(iOS获取App崩溃信息)