1. App 启动和退出事件采集时机

对于 App 启动($AppStart)和 App 退出 ($AppEnd)事件而言,归根结底就是判断当前应用程序是处于前台还是处于后台。当应用启动或者从后台切换到前台时,采集 App 启动事件,当应用切换到后台时,采集 App 退出事件。

2. 判断应用是否在前台

Android 系统本身并没有给应用程序提供相关的接口来判断这些状态,所以只能借助其他方式来间接判断。业界也有很多种方案用来判断一个应用程序是处于前台还是后台。通过对比,采用 ActivityLifecycleCallbacks 来判断 App 是否在前台的方式实现比较简单,代码量少。ActivityLifecycleCallbacks 是 Application 的一个内部接口,是从 API 14(即 Android 4.0)开始提供的。Application 类通过 ActivityLifecycleCallbacks 接口提供了一系列的回调方法,用于让开发者可以对 Activity 的所有生命周期事件进行集中处理(或称监控)。我们可以通过 Application 注册 ActivityLifecycleCallbacks 接口,以实现对所有 Activity 生命周期的回调。在 Activity 生命周期回调中添加一个是否有前台页面的标记即可判断。

如果有新的页面显示时,则存储一个标记位 startActivityCount 来标记已有新的页面进来。即在 ActivityLifecycleCallbacks 的 onActivityStarted 方法中,执行 startActivityCount++ 来标记,如果 startActivityCount 等于 1 时,则采集 App 启动事件;在应用进入后台,即在 ActivityLifecycleCallbacks 对应的 onActivityStopped 方法中执行 startActivityCount- – 清除标记,当 startActivityCount  小于等于 0 时则采集 App 退出事件 。 在 Android 应用程序中可以有多个进程存在的,所以在 startActivityCount 计数时,需要考虑多进程的问题。一般情况下,解决跨进程数据共享的问题,普遍采用的是 ContentProvider + SQLite3 方案,但是使用 SQLite3 数据库来存储一些简单的数据和标记位,明显太过重量级了。通常在 Android 系统以及应用程序开发中,针对一些比较简单的数据的存储,一般采用 SharedPreferences,从而可以做到快速读写。所以可以采用 “ContentProvider + SharedPreferences” 的方案来解决跨进程数据共享的问题。在多进程情况下,通过这种方式及解决了多进程操作标记值的问题。

3. 神策 Android SDK 中引入 Session 概念

神策 Android SDK 引入了 Session 的概念。简单理解就是:对于一个应用程序,当它的一个页面退出了,如果在 30s 之内没有新的页面打开,我们就认为这个应用程序处于后台了(触发 $AppEnd 事件);当它的一个页面显示出来了,如果与上一个页面的退出时间的间隔超过了 30s,我们就认为这个应用程序重新处于前台了(触发 $AppStart 事件)。此时,Session 的间隔我们是以 30s 为例。

具体做法是首先注册一个 Application.ActivityLifecycleCallbacks 回调,用来监听应用程序内所有 Activity 的生命周期。然后我们再分两种情况分别进行处理。在页面退出的时候,我们会启动一个 30s 的倒计时,如果在30s 之内有新的页面进来(或显示),则存储一个标记位来标记已有新的页面进来,从而可以取消上个页面退出时启动的倒计时。如果 30s 之内没有新的页面进来(比如用户按 Home 键/返回键退出应用程序、应用程序发生崩溃、应用程序被强杀),则触发 $AppEnd 事件或者在下次启动的时候补发一个 $AppEnd 事件,对于一些特殊的情况(应用程序发生崩溃、应用程序被强杀),应用程序可能停止运行了,导致无法及时触发 $AppEnd 事件,只能在用户下次启动应用程序的时候进行补发。如果用户再也不去启动应用程序或者将应用程序卸载,就会导致“丢失” $AppEnd 事件。

 

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