iOS应用程序连续闪退时上报崩盘日志的方法详解

  

  

当一个iOS应用程序崩溃时,系统会创建一份事故日志保存在设备上。这份崩盘日志记录着应用程序崩溃时的信息,通常包含着每个执行线程的栈调用信息(低内存闪退日志例外),对于开发人员定位问题很有帮助。

  

为保障线上应用的用户体验,我们一般都会对线上应用的崩溃率做实时监控,一旦检测到峰值,可以即刻调查原因,但这一切的前提是崩盘日志能够准确上报。

  

<强>崩盘日志上报有两个难点:

  
      <李>事故处理程序安装之前的代码要绝对稳定
      如果日志采集器还没成功启动就崩溃了,自然什么日志也无法采集到。这一点并没有太多技巧可言,只能严格限制处理程序启动之前可以执行的代码。   <李>应用无限循环崩溃时上报
      崩盘日志上报时,会发送网络请求,如果请求成功之前应用又发生事故该如何处理?用户甚至会陷入无限循环的崩溃中。
      李   
  

这篇文章介绍下出现第二种情况时,如何准确上报崩盘日志。

  

首先我们需要一种比较可靠的方式,可以在应用程序启动时判断上次是否发生了启动崩溃。介绍一个可行的思路。

  


  

  

连续闪退包含两个元素,闪退和连续。只有这两个元素同时具备时,才会影响我们的日志上传。闪退的定义可以简单为

  
  

应用程序崩溃时间之处;应用程序启动时间& lt;=5 s(或者其他阈值)
  

     

连续的定义为,至少接连出现两次或者以上。一般2次就够了,很多时候用户连续经历两次闪退,就会放弃尝试。

  

我们可以通过记录若干个特殊的时间点时间戳来试图还原程序崩溃场景下的生命周期。

  
      <李>应用程序启动时间戳、定义为launchTs
      应用每次启动时,记录当前时间,写入时间数组。   <李>应用程序崩溃的时间戳,定义为crashTs
      应用每次启动时,通过崩溃采集库,获取上次事故报告的时间戳,写入时间数组。   <李>应用正常退出时间戳、定义为terminateTs
      应用在接收到UIApplicationWillTerminateNotification通知时,记录当前时间戳,写入时间数组。注意,还有很多种应用退出行为的时间戳是无法被准确记录的。   
  

之所以要记录terminateTs,是为了排除一种特殊情况,即用户启动应用之后立即手动杀应用。如果我们正确记录了上面三个时间戳,那么我们可以得到一个与应用程序崩溃行为相关的时间线。比如:

  
  

launchTs=比;crashTs=比;launchTs=比;terminateTs
  

     

或者   

  

launchTs=比;launchTs=比;launchTs
  

     

或者   

  

launchTs=比;crashTs=比;launchTs=比;crashTs=比;launchTs
  

     

请自行脑洞上面三种时间线的行为特征。很明显,第三种时间线看上去是连续崩溃了两次。我们只需要加上时间间隔判断,就能得知是否为连续两次闪退了。注意,如果两个crashTs之间如果存在terminateTs,则不能被认为是连续闪退。检测代码比较简单,我就不贴了。

  

这个时间线只是记录与事故相关的程序启动和退出行为,还有很多特殊的时间点没有记录,比如应用在前台发生的记忆(FOOM),应用在前台主线程卡住被系统看狗杀掉,iOS系统升级时应用被强杀,应用程序从应用商店升级时被强杀等等,这些特殊的时间点都没有记录,不过这些并不影响我们的应用程序连续闪退检测,所以可以忽略。

  

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

  

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

  


  

  

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

  

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

  

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

  

iOS应用程序连续闪退时上报崩盘日志的方法详解