iOS Crash闪退日志的捕获和上传至服务器

今天我要讲的是app的闪退信息的捕获,以及日志上传。

涉及的技术点

  • 异常处理
  • 捕获方式
  • 信号量
  • 闪退日志上传

在APP开发中,对于开发者或者使用者最不能接受的bug就是APP崩溃,所以对于APP闪退的问题追踪非常重要,有利于尽快的修复这个问题。现在有许多的第三方崩溃日志统计服务sdk,如:Bugtags,腾讯的Bugly,友盟等。这些服务商提供了非常便捷的集成方式。关于如何使用这些工具,请看以上的官网就可以,有很详细的介绍,今天我们来讲一下如何实现这一项技术。

提出问题:

如何捕获app闪退的原因?如何将闪退的原因发送到服务器?

异常处理

在iOS中,异常的处理有两种:预先设置捕获的异常未知异常

预先设置捕获的异常的处理

该种异常是通过标准的@try @catch(id exception) @finally 来处理的。

伪代码如下:
@try {
// 有可能出现异常的代码块,可以创建异常对象并且手动抛出异常或者在执行可能出现的代码会自动触发并抛出异常

} @catch (id exception)  // 大部分情况下是通过直接指定异常参数@catch(NSException *e)
{
// 捕获到异常的处理措施

} @finally {
// 无论有无异常都要执行的操作,例如某些资源的释放等。

}

对于@try代码块中出现的异常,会将异常发送到@catch代码块中进行处理。在这种情况下出现的异常情况,不会将异常发送到系统级别,因此不会引起系统的闪退情况。

示例代码:
- (void)funtion {

    NSMutableArray *mutaArray = [NSMutableArray arrayWithCapacity:0];
    NSString *str = nil;

    for (int i = 0; i < 8; i++) {
        @try {
            if (i == 7) {
                [mutaArray addObject:str];
            } else {
                [mutaArray addObject:@(i)];
            }
        } @catch(NSException *e) {
            NSLog(@"%@",e);
        }
        @finally {
            NSLog(@"finally");
        }
    }
    }

当系统执行到该函数的时候,系统并不会导致闪退,因为已经对抛出的异常进行了异常捕获。

未知异常的处理

所谓未知异常就是没有对可能发生异常的程序采取异常捕获机制,出现异常后引起APP的闪退。这类异常的出现是由于没有对局部对异常进行处理,则系统默认将异常传递个系统级别。
示例代码:

- (void)funtionWithUncaughtException {

NSMutableArray *mutaArray = [NSMutableArray arrayWithCapacity:0];
NSString *str = nil;

for (int i = 0; i < 8; i++) {
        if (i == 7) {
            [mutaArray addObject:str];
        } else {
            [mutaArray addObject:@(i)];
        }
}
}

上述代码引起APP闪退,在终端如下的提示:

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil’
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010eda11cb __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010e703f41 objc_exception_throw + 48
    2   CoreFoundation                      0x000000010ede0e8c _CFThrowFormattedException + 194
    3   CoreFoundation                      0x000000010ecd06f1 -[__NSArrayM insertObject:atIndex:] + 1233
    4   CrashLogDemo                        0x000000010ddf7ccb -[ViewController funtion] + 123
    5   CrashLogDemo                        0x000000010ddf766a -[ViewController TestCrash:] + 58
    6   UIKit                               0x00000001109779bd -[UIApplication sendAction:to:from:forEvent:] + 83
    7   UIKit                               0x0000000110aee183 -[UIControl sendAction:to:forEvent:] + 67
    8   UIKit                               0x0000000110aee4a0 -[UIControl _sendActionsForEvents:withEvent:] + 450
    9   UIKit                               0x0000000110aed3cd -[UIControl touchesEnded:withEvent:] + 618
    10  UIKit                               0x00000001109ebd4f -[UIWindow _sendTouchesForEvent:] + 2807
    11  UIKit                               0x00000001109ed472 -[UIWindow sendEvent:] + 4124
    12  UIKit                               0x0000000110992802 -[UIApplication sendEvent:] + 352
    13  UIKit                               0x000000012851e7b3 -[UIApplicationAccessibility sendEvent:] + 85
    14  UIKit                               0x00000001112c4a50 __dispatchPreprocessedEventFromEventQueue + 2809
    15  UIKit                               0x00000001112c75b7 __handleEventQueueInternal + 5957
    16  CoreFoundation                      0x000000010ed442b1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    17  CoreFoundation                      0x000000010ede3d31 __CFRunLoopDoSource0 + 81
    18  CoreFoundation                      0x000000010ed28c19 __CFRunLoopDoSources0 + 185
    19  CoreFoundation                      0x000000010ed281ff __CFRunLoopRun + 1279
    20  CoreFoundation                      0x000000010ed27a89 CFRunLoopRunSpecific + 409
    21  GraphicsServices                    0x0000000113dad9c6 GSEventRunModal + 62
    22  UIKit                               0x0000000110975d30 UIApplicationMain + 159
    23  CrashLogDemo                        0x000000010ddf859f main + 111
    24  libdyld.dylib                       0x0000000110585d81 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

以上介绍了app开发的两种异常,总结下:

1.预先设置对异常的捕获,不会引起app的闪退。没有对可能发生异常的地方进行处理机制,会引起app的闪退。
2.出现app闪退有一个比较好的方面就是容易排查系统存在的bug。
3.若是不采用局部异常捕获机制,又想要减少系统闪退的概率只能采用更多的逻辑判断,
    例如:if(str) {
        [mutaArray addObject:str];
    }

捕获方式

预先设置捕获的异常的捕获

预先设置捕获的异常的捕获在@cathch代码块内捕获处理的。

未知异常的捕获

在默认情况下,系统发生了未知异常,系统会捕获该异常并且退出app。发生异常后,系统会创建一个NSExcetion对象,并且在异常出抛出,等待有接受者,若没有传递给系统处理。那么如何来获取这个未知异常呢?该问题问的好!利用如下函数来解决这个问题:

FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);

该函数是一个全局的函数,该函数一般在app开始的时候调用,该函数的意义:设置未知异常的捕获函数,参数是未知异常处理函数的函数名,该未知异常处理函数的模式如下:
typedef void NSUncaughtExceptionHandler(NSException *exception);
未知异常处理函数的示例代码:

void uncaughtExceptionHandler(NSException *exception) {
   // 在app退出前的一些处理任务,系统会等待该函数的执行完毕 
   // NSLog(@“CRASH: %@“, exception);
   // NSLog(@“Stack Trace: %@“, [exception callStackSymbols]);
   // Internal error reporting 
}

调用方式:

NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler); // 该函数可在任务未知调用。

完成上述的异常处理函数的定义和调用后,若再发生系统的未知异常的情况下,系统首先将异常传递个该函数,执行完该函数后app退出。因此,我们可以在这个函数内做一些业务处理,例如记录或者传递异常等。

信号量机制

信号量的内容并不是这篇文章的重点,由于使用到了,所以要提及一下。先做一个简单的介绍,信号量主要用作多个线程对某项资源的使用,信号量代表资源的可利用数目,该数目为非负数,当某个线程要使用某项资源的时候,资源若是大于0,则可以使用,否则需要等待其他的线程释放该资源后才可使用。
在ios开发中使用GCD技术的dispacth semaphore,具体的请自行google:ios semaphore,会有诸多的描述,稍后我也会在关于GCD的博客中讲述.

闪退日志上传

在未知异常处理的部分说过,在异常处理的处理函数中可以对异常做一些处理,例如对异常的记录,上传。对于异常的记录我就不做额外的赘述,如同一般的数据存储一样。关键是需要进行同步操作,避免在操作的过程中跳出异常处理函数。
对于异常的上传也是这样的,需要通过同步来完成对数据的上传操作。现在的一个问题:

现在ios的服务器上传需要同步操作,而NSURLSession现在服务器请求都是异步操作,就会发生程序已经退出,将停止对数据的发送

因此解决的方式就是使用信号量机制,代码如下:

// 通过post 或者 get 方式来将异常信息发送到服务器

- (void)sendCrashLog:(NSString *)crashLog {

dispatch_semaphore_t semophore = dispatch_semaphore_create(0); // 创建信号量
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:nil delegateQueue:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"your url"]];
[request setHTTPMethod:@"POST"];
request.HTTPBody = [[NSString stringWithFormat:@"crash=%@",crashLog]dataUsingEncoding:NSUTF8StringEncoding];
[[session dataTaskWithRequest:request 
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    NSLog(@"response%@",response);
    dispatch_semaphore_signal(semophore); // 发送信号
}] resume];
dispatch_semaphore_wait(semophore, DISPATCH_TIME_FOREVER); // 等待

}

异常处理调用的地方

void uncaughtExceptionHandler(NSException *exception) {
    NSLog(@“CRASH: %@“, exception);
    NSLog(@“Stack Trace: %@“, [exception callStackSymbols]);
    NSLog(@“oh,app,you dead”);
    CrashLogSender *logSender = [[CrashLogSender alloc]init];
    [logSender sendCrashLog:exception.description];
}

到此,对该部分内容的描述已经结束,稍后会把相关的代码上传到git上,敬请批评指正与交流。

你可能感兴趣的:(IOS,iOS开发错误调试)