1 应用程序状态

在分析如何采集启动和退出事件之前,我们先简单介绍一下 iOS 应用程序状态相关的内容。

大家都知道,对于一个标准的 iOS 应用程序来说,在不同的时期会有不同的状态,如下图所示。

正常情况下,iOS 应用程序主要有五种常见的状态。

  • Not running

非运行状态。指应用程序还没有被启动,或者已被系统终止。

  • Inactive

前台非活动状态。指应用程序即将进入前台状态,但当前未接收到任何事件(它可能正在执行其它代码)。应用程序通常只在转换到其它状态时才会短暂地进入这个状态。

  • Active

前台活跃状态。指应用程序正在前台运行,可接收事件并进行处理。这也是一个 iOS 应用程序处于前台的正常模式。

  • Background

进入后台状态。指应用程序进入了后台并可执行代码。大多数应用程序在被挂起前都会短暂地进入这个状态。

  • Suspended

挂起状态。指应用程序进入后台但没有执行任何代码,系统会自动的将应用程序转移到此状态,并且在执行此操作之前不会通知应用程序。挂起时,应用程序会保留在内存中,但不执行任何代码。当系统出现内存不足情况时,系统可能会在未通知应用程序的情况下清除被挂起的应用程序,为前台应用程序尽可能腾出更多的运行资源。

在应用程序不同状态转换的过程中,系统会回调实现了 UIApplicationDelegate 协议的类的一些方法(如在我们 Demo 中, Xcode 默认创建的 AppDelegate 类),并发送相应的本地通知(系统会先回调相应的方法,待回调方法执行完后,再发送相应的通知),回调方法和本地通知的对应关系可参考如下表所示。

关于 iOS 应用程序状态更详细的内容,也可参考苹果官网文档介绍。

2 应用程序退出

通过上面介绍的内容可知,当一个 iOS 应用程序退出时,就意味着该应用程序进入了“后台”,即处于 Background 状态。因此,对于实现 $AppEnd 事件的全埋点,我们只需要注册监听 UIApplicationDidEnterBackgroundNotification 通知,然后在收到通知时触发 $AppEnd 事件,即可达到 $AppEnd 事件全埋点的效果。

3 应用程序启动

应用程序的启动,一般情况下,大致可以分为两类场景:

  • 冷启动
  • 热启动(从后台恢复)

不管是冷启动还是热启动,触发 $AppStart 事件的时机,都可以理解成是当“应用程序开始进入前台并处于活动状态”,也即前文介绍的 Active 状态。

因此,为了实现 $AppStart 事件的全埋点,我们可以注册监听 UIApplicationDidBecomeActiveNotification 本地通知,然后在其相应的回调方法里触发 $AppStart 事件。

通过测试可以发现,仍有以下几个特殊场景存在问题:

  • 下拉通知栏并上滑,会触发 $AppStart 事件
  • 上滑控制中心并下拉,会触发 $AppStart 事件
  • 双击 Home 键进入切换应用程序页面,最后又选择当前应用程序,会触发 $AppStart 事件

以上几个场景均会触发 $AppStart 事件,明显与实际情况有所不符。

那这些现象是什么原因导致的呢?

我们继续分析可以发现以下几个现象:

  • 下拉通知栏时,系统会发送 UIApplicationWillResignActiveNotification 通知;上滑通知栏时,系统会发送 UIApplicationDidBecomeActiveNotification 通知
  • 上滑控制中心时,系统会发送 UIApplicationWillResignActiveNotification 通知;下拉控制中心时,系统会发送 UIApplicationDidBecomeActiveNotification 通知
  • 双击 Home 键进入切换应用程序页面时,系统会发送 UIApplicationWillResignActiveNotification 通知,然后选择当前应用程序,系统会再发送 UIApplicationDidBecomeActiveNotification 通知

很容易总结出规律:在以上几个场景下,系统均是先发送UIApplicationWillResignActiveNotification 通知,然后再发送 UIApplicationDidBecomeActiveNotification 通知。而我们又是通过注册监听 UIApplicationDidBecomeActiveNotification 通知来实现 $AppStart 事件全埋点,因此均会触发 $AppStart 事件。

那如何解决这个问题呢?

在解决这个问题之前,我们先看另一个现象:不管是冷启动还是热启动,系统均没有发送 UIApplicationWillResignActiveNotification 通知。

因此,只要在收到 UIApplicationDidBecomeActiveNotification 通知时,判断之前是否收到过 UIApplicationWillResignActiveNotification 通知,若没有收到,则触发  $AppStart 事件;若已收到,则不触发  $AppStart 事件。这样即可解决上面的问题。

4 被动启动

被动启动?什么意思?完全没有听说过!

你若有这些疑问,那就对了!因为这完全是我们神策数据自创的一个名词。

在 iOS 7 之后,苹果新增了后台应用程序刷新功能,这个功能允许操作系统在一定的时间间隔内(这个时间间隔根据用户不同的操作习惯而有所不同,可能是几个小时,也可能是几天),拉起应用程序并同时让其进入后台运行,以便应用程序可以获取最新的数据并更新相关内容,从而可以确保用户在打开应用程序的时候可以第一时间查看到最新的内容。例如新闻或者社交媒体类型的应用程序,可以使用这个功能在后台获取到最新的数据内容,在用户打开应用程序时可以缩短应用程序启动和获取内容展示的等待时间,最终提升产品的用户体验。

后台应用程序刷新,对于用户来说可以缩短等待时间;对于产品来说,可以提升用户体验;但对于数据采集 SDK 来说,可能会带来一系列的问题,比如:当系统拉起应用程序(会触发 $AppStart 事件)并同时让其进入后台运行时,应用程序的第一个页面(UIViewController)也会被加载,也即会触发一次页面浏览事件($AppViewScreen 事件),这明显是不合理的,因为用户并没有打开应用程序,更没有浏览第一个页面。其实,整个后台应用程序刷新的过程,对于用户而言,完全是透明的、无感知的。

因此,在实际的数据采集过程中,我们需要避免这种情况的发生,以免影响到正常的数据分析。

我们把由 iOS 系统触发的应用程序自动进入后台运行的启动,称之为(应用程序的)被动启动,使用 $AppStartPassively 事件来表示。

后台应用程序刷新是最常见的造成被动启动的原因之一。而后台应用程序刷新只是其中一种后台运行模式,还有一些其它后台运行模式同样也会触发被动启动,我们下面会详细介绍。

使用 Xcode 创建新的应用程序,默认情况下后台刷新功能是关闭的,我们可以在 Capabilities 标签中开启 Background Modes,然后就可以勾选所需要的功能了,如下图所示:

通过上图我们可以看到,还有如下几种后台运行模式,它们同样也会导致触发被动启动($AppStartPassively 事件):

  • Location updates:此模式下,会由于地理位置变化而触发应用程序启动
  • Newsstand downloads:这种模式只针对报刊杂志类应用程序,当有新的报刊可下载时,会触发应用程序启动
  • External Accessory communication:此模式下,一些 MFi 外设通过蓝牙或者 Lightning 接头等方式与 iOS 设备连接,从而可在外设给应用程序发送消息时,触发对应的应用程序启动
  • Uses Bluetooth LE accessories:此模式与 External Accessory communication 类似,只是无需限制 MFi 外设,而需要的是 Bluetooth LE 设备
  • Acts as a Bluetooth LE accessory:此模式下,iPhone 作为一个蓝牙外设连接,可以触发应用程序启动
  • Background fetch:此模式下,iOS 系统会在一定的时间间隔内触发应用程序启动,去获取应用程序数据
  • Remote notifications:此模式是支持静默推送,当应用程序收到这种推送后,不会有任何界面提示,但会触发应用程序启动

 

每日一问的答案中可能无法全完解读这个问题,如果您是相关技术专家或者是对本问题有自己的见解,欢迎带着「批判性」的态度阅读,指正其中的不足。