今天我要讲的是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上,敬请批评指正与交流。