《Android全埋点解决方案》 —3 $AppStart、$AppEnd全埋点方案
第3章
$AppStart、$AppEnd全埋点方案
对于$AppStart 和$AppEnd 事件而言,归根结底就是判断当前应用程序是处于前台还是处于后台。而 Android 系统本身并没有给应用程序提供相关的接口来判断这些状态,所以我们只能借助其他方式来间接判断。
目前,业界也有很多种方案用来判断一个应用程序是处于前台还是后台,以 Github 上的一个开源项目为例:https://github.com/wenmingvs/AndroidProcess。
这个开源项目提供了 6 种方案。这 6 种方案的综合对比可以参考表3-1。
表3-1 6种方案的对比
以上6种方案,各有优缺点,但都无法解决我们最关心的几个问题:
应用程序如果有多个进程该如何判断?
应用程序如果发生崩溃了该如何判断?
应用程序如果被强杀了又该如何判断?
3.1 原理概述
针对上面列出的3个问题,我们下面将一一进行分析并解决。
(1)应用程序如果有多个进程该如何判断是处于前台还是处于后台?
众所周知,一个 Android 应用程序是可以有多个进程同时存在的,所以这就加大了我们判断一个应用程序是处于前台还是处于后台的难度,继而导致很多常见的判断方案也都会失效。
其实,对于这个问题,可以归于应用程序多进程间的数据共享问题。
Android系统中支持多进程通信方式主要有以下几种,它们各有优缺点。
AIDL
AIDL 的功能相对来说比较强大,支持进程间一对多的实时并发通信,并且可以实现 RPC(远程过程调用)。
Messenger
Messenger支持一对多的串行实时通信,它相当于是 AIDL的简化版本。
Bundle
Bundle 是Android 系统中四大组件的进程间通信方式,目前只能传输 Bundle 支持的数据类型,比如 String、int 等。
ContentProvider
ContentProvider是一个非常强大的数据源访问组件,主要支持 CRUD操作和一对多的进程间数据共享,例如我们的应用访问系统的图库数据。
BroadcastReceiver
BroadcastReceiver即广播,目前只能支持单向通信,接收者只能被动地接收消息。
文件共享
文件共享主要适用于在非高并发情况下共享一些比较简单的数据。
Socket
Socket 主要通过网络传输数据。
我们目前的方案主要是采用ContentProvider 机制来解决进程间的数据共享问题。ContentProvider 是基于 Binder 机制封装的系统组件,天生就是用来解决跨进程间的数据共享问题的。另一方面,Android 系统也提供了针对ContentProvider 的数据回调监听机制—即 ContentObserver,这样就更加方便我们来处理跨进程间的数据通信方面的问题。
一般情况下,解决跨进程数据共享的问题,普遍采用的是 ContentProvider + SQLite3方案,但是鉴于目前我们面临的实际情况,使用 SQLite3数据库来存储一些简单的数据和标记位,明显太过重量级了。通常在 Android 系统以及应用程序开发中,针对一些比较简单的数据的存储,一般采用SharedPreferences,从而可以做到快速读写。所以我们目前采用“ContentProvider + SharedPreferences”的方案来解决跨进程数据共享的问题。
(2)应用程序如果发生崩溃或者被强杀了该如何判断该应用程序是处于前台还是处于后台?
对于应用程序发生崩溃或者应用进程被强杀的场景,我们引入了 Session 的概念。简单理解就是:对于一个应用程序,当它的一个页面退出了,如果在 30s 之内没有新的页面打开,我们就认为这个应用程序处于后台了(触发$AppEnd事件);当它的一个页面显示出来了,如果与上一个页面的退出时间的间隔超过了 30s,我们就认为这个应用程序重新处于前台了(触发$AppStart事件)。此时,Session 的间隔我们是以 30s 为例。
总体来说,我们首先注册一个Application.ActivityLifecycleCallbacks 回调,用来监听应用程序内所有 Activity 的生命周期。然后我们再分两种情况分别进行处理。
在页面退出的时候(即onPause生命周期函数),我们会启动一个 30s 的倒计时,如果 30s 之内没有新的页面进来(或显示),则触发 $AppEnd 事件;如果有新的页面进来(或显示),则存储一个标记位来标记已有新的页面进来。这里需要注意的是,由于 Activity 之间可能是跨进程的(即给 Activity 设置了 android:process 属性),所以标记位需要实现进程间的共享,即通过 ContentProvider + SharedPreferences 来进行存储。然后通过 ContentObserver 监听到新页面进来的标记位改变,从而可以取消上个页面退出时启动的倒计时。如果 30s 之内没有新的页面进来(比如用户按 Home 键/返回键退出应用程序、应用程序发生崩溃、应用程序被强杀),则会触发 $AppEnd 事件,或者在下次启动的时候补发一个 $AppEnd 事件。之所以要补发 $AppEnd 事件,是因为对于一些特殊的情况(应用程序发生崩溃、应用程序被强杀),应用程序可能停止运行了,导致我们无法及时触发 $AppEnd 事件,只能在用户下次启动应用程序的时候进行补发。当然,如果用户再也不去启动应用程序或者将应用程序卸载,就会导致“丢失” $AppEnd 事件。
在页面启动的时候(即onStart生命周期函数),我们需要判断一下与上个页面的退出时间间隔是否超过了 30s,如果没有超过 30s,则直接触发 $AppViewScreen 事件。如果已超过了 30s,我们则需要判断之前是否已经触发了 $AppEnd 事件(因为如果 App 崩溃了或者被强杀了,可能没有触发 $AppEnd 事件),如果没有,则先触发 $AppEnd 事件,然后再触发 $AppStart 和 $AppViewScreen事件。
- 点赞
- 收藏
- 关注作者
评论(0)