对于 $AppStart 和 $AppEnd 而言,归根结底就是判断当前 App 是处于前台还是处于后台。而 Android 系统本身没有给 App 提供相关的接口来判断这些状态,所以我们只能借助其它方式来间接判断。目前,业界也有很多种方法来判断一个 App 是处于前台还是后台,以 Github 上的一个开源项目为例:https://github.com/wenmingvs/AndroidProcess 这个开源项目目前提供了 6 种方案:

关于这 6 种方案详细源码,可以参考开源项目。 以上这 6 种方案,各有优缺点,但同时,都无法解决下面两个问题:

• App 有多个进程如何判断?

• App 崩溃或者被强杀了如何判断?

原理概述

对于多进程间的数据共享问题,我们采用 ContentPro- vider 机 制 来 解 决。一 方 面 ContentProvider 是 基 于 Binder 机制封装的系统组件,目的就是解决跨进程的数据共享问题。另一方面,Android 系统提供了针对 ContentProvider 的 数 据 回 调 监 听 ,即 ContentOb- server,这样就更加能满足跨进程间的数据通信。

一般情况下,针对跨进程数据共享采用的是 ContentPro- vider + SQLite 方案,但是基于我们的实际情况,使用 SQLite 数据库存储一些简单数据、标记位明显太过重量级了。通常在 Android 系统中,针对一些比较简单数据存储,一般是采用 SharedPreferences 进行快速读写。

所以在这里我们采用的跨进程数据共享实现方式是基于 ContentProvider + SharedPreferences 的方案。对于 App 崩溃或者应用进程被强杀的场景,我们引入了 Session 的概念。即:对于一个 App,当一个页面退出了, 如果 30s 之内没有新的页面打开,我们就认为 App 处于 后台了;当一个页面显示了,如果与上一个页面退出时间的间隔超过 30s,我们就认为 App 重新处于前台了。

我们首先注册一个 ActivityLifecycleCallbacks 回调,来监听应用程序内所有 Activity 的生命周期。处理业务时涉及到标记位的保存以及跨进程间的数据通信,我们采用 ContentProvider + SharedPreferences 的方式实现进程间数据共享,同时注册 ContentObserver 来监听跨进程间的数据通信。

下面分两种情况进行处理:

在页面退出的时候(onPause),我们启动一个倒计时 30s 定时器,如果 30s 之内没有新的页面显示,则触发 $AppEnd 事件;如果有新的页面进来,我们存储一个标记位来标记新页面进来。这里需要注意的是,由于 Activity 之间可能是跨进程的,所以标记位需要实现进程 间的共享,即通过 ContentProvider + SharedPreferenc- es 进行存储。然后通过 ContentObserver 监听到新页面进来的标记位改变,然后取消定时器。如果 30s 之内没有新的页面进来(比如用户按 Home 键 / 返回键退出 App、 App 崩溃、App 被强杀),我们会下次启动的时候补发一 个 $AppEnd 事件。

在下次页面启动的时候(onStart),我们判断一下与上个页面的退出时间间隔是否超过了 30s,如果没有超过 30s, 则无需补发 $AppEnd 事件,直接触发 $AppScreen 事件。 接下来判断是否已触发 $AppEnd 事件的标记位,如果标记位为 true,则触发 $AppStart 事件,反之不触发;如果超过了 30s,我们判断一下是否已经触发了 $AppEnd 事件,如果没有则先触发 $AppEnd 事 件,然 后 再 触 发 $AppStart 和 $AppScreen 事件。

实现步骤

完整的项目源码后续会 release 给大家。

知识点

ContentProvider

注:该内容来自神策数据用户行为洞察研究院出品的《Android 全埋点解决方案》白皮书,查看完整白皮书可点击《Android 全埋点解决方案》

更多白皮书、报告、干货和案例,可以关注“神策数据”和“用户行为洞察研究院”公众号了解~