iOS App连续闪退时上报crash日志的方法详解

2020-01-21 05:01:39丽君

这个时间线只是记录与 crash 相关的 App 启动和退出行为,还有很多特殊的时间点没有记录,比如 App 在 前台发生 out of memory(FOOM),App 在前台 main thread 卡住被系统 Watch Dog 杀掉,iOS 系统升级时 App 被强杀,App 从 AppStore 升级时被强杀等等,这些特殊的时间点都没有记录,不过这些并不影响我们的 App 连续闪退检测,所以可以忽略。

这里指的注意的是,因为启动时要从 disk 读取时间线记录,涉及磁盘读写,会对 App 的启动时间产生影响,一个优化点是,在每次写入时间点移除掉较老的 timestamp,比如只记录最近 5 个时间戳。或者在没有读取到 crash 日志时,甚至不用启动连续闪退检测的整个流程。

接下来,我们看假设检测到连续闪退,我们如何继续上传日志。

同步等待 Crash 日志上传

最直白的方式,在 App 的代码继续执行之前,先等待日志上传成功。

把网络请求改成同步的?这会卡住 UI 线程,网络差的场景下会被系统 watch dog 强杀,显然不可取。

我们可以依旧保持异步网络请求,但是,暂时中断 UI 线程的流程,让整个 App 处于 UI 线程的 runloop 等待中,一旦网络请求成功,则跳回到 UI 线程的原有代码流程。

看着简单的实现,有几个细节需要注意。首先我们需要增加一个 App 交互,一旦进入 runloop 等待,展示一个 loading 界面,告知用户耐心等待。其次,这个等待时间不能过长,我个人建议不超过 5s,一旦超过 5s,无论 crash 日志上传的 request 是否成功,都恢复 App 原有代码流程。5s 内日志都无法上传成功的情况应该比较小,除非日志文件过大。

这种做法缺陷也很明显,一是改动比较大(修改了原有代码流程),二是需要增加新的 UI 交互,三是延长了用户的等待时间。

我们来看另一种取巧的做法。

启用后台进程上传 Crash 日志

其实最理想的日志上传,是将上传的 request 放到另一个不同的进程,那么即使 App 又发生闪退,也不会影响到另一个进程代码的执行。

问题是,iOS app 都处于 sandbox 环境下,系统不允许代码 fork 一个新进程。

幸运的是,从 iOS 8 开始,系统对 NSURLSession 新增了一个 background session 特性。这个特性允许 NSURLSession 将网络请求放入到一个单独的进程中执行。我个人感觉,这个特性设计,原本是为了增强某些 App 后台下载音视频等资源的体验。我实际测试下来,发现不管下载或者是上传,我们都可以将网络请求放入另一个进程。代码也很简单,比如我写一段如下的测试代码:


NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.mrpeak.background.crashupload"];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue new]];
NSURL *url = [NSURL URLWithString:@"https://www.easck.com/photo-1515816949419-7caf0a210607?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=f46b60857b4826e733da34993ec26a2f&auto=format&fit=crop&w=1534&q=80"];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url];
[task resume];

exit(0);