《Android 全埋点解决方案》

意外得到本书,迫不及待的翻阅了一下,说实话,本书就是某公司 SDK 的源码介绍,所以我觉得这不是本好书,源码 Github 上就有不用全部搬到书上吧。但不可否认,作者的技术能力值得我学习。

一、埋点是什么

通用数据:

事件 描述
冷启动事件 APP 第一次启动时的,版本号、设备ID、渠道、内存使用情况,磁盘使用情况等信息
前后台事件 APP 进入前台或者后台
页面事件 页面(Activity 或 Fragment)显示(Show)/隐藏(Hide)
控件点击事件 某个控件(包括页面上控件和弹窗中控件)被用户点击
列表浏览事件[可选] 某个列表的哪些条目被用户浏览了
位置事件[可选] 上报用户地理位置信息
其它事件 省略描述

采集行为数据时,通常需要在 Web 页面/ APP 里面添加一些代码,当用户的行为达到某种条件时,就会向服务器上报用户的行为,添加这些代码的过程就可以叫做“埋点”。埋点,是对网站、APP 或者后台等应用程序进行数据采集的一种方法。通过埋点,可以收集用户在应用中的产生行为,进而用于分析和优化产品后续的体验,也可以为产品的运营提供数据支撑,其中常见的指标有 PV(Page View)访问量、UV(Unique Visitor)独立访客、页面时长和按钮的点击等。

互联网产品对用户行为的数据有多渴望,相信不用多说了,上至微信,下至小作坊,每一个公司每一个有稍微专业点的产品经理或者技术人员,都想知道做出来的产品用户到底有没有在用那些功能用得多,用的频率怎样。淘宝、天猫、京东这些 APP 为什么每次打开的时候看到的东西感觉都挺熟悉?是不是上几次查询的商品?这些都是典型的用户行为分析得出来的定制结果。

数据埋点是数据分析的准备工作,只有埋点了才可以获得数据进行分析。数据这件事情要做好,最重要的是数据源,只要数据源头解决好了,后面的分析处理都比较好办。

二、埋点方式

数据的采集有三种方式:

  • 代码埋点
  • 工具导入
  • 全埋点

2.1 代码埋点

代码埋点是指开发人员按照产品/运营的需求,在 Web 页面/ APP 的源码里面添加行为上报的代码,当用户的行为满足某一个条件时,这些代码就会被执行,向服务器上报行为数据。这种方案是最基础的方案,每次增加或者修改数据上报的条件,都需要开发人员的参与,并且只能在下一个版本上线后才能看到效果。很多公司都提供了这类数据上报的 SDK,将行为上报的后台服务器接口封装成了简单的客户端 SDK 接口。开发者可以通过嵌入这类 SDK,在埋点的地方调用少量的代码就可以上报行为数据。

2.2 工具导入

工具导入指利用第三方统计工具插件(如友盟、百度移动、魔方、App Annie、talking data等)手机想要获得的数据,简单方便地达到可视化埋点,但是这样的话,数据也被第三方掌握了。

2.3 全埋点

全埋点指的是将 Web 页面/ APP 内产生的所有的、满足某个条件的行为,全部上报到后台服务器。例如把 APP 中所有的按钮点击都进行上报,然后由产品/运营去后台筛选所需要的行为数据。这种方案的优点非常明显,就是可以不用在新增/修改行为上报条件时,再找开发人员去修改埋点的代码。然而它的缺点也和优点一样明显,那就是上报的数据量比代码埋点大很多,里面可能很多是没有价值的数据。

三、Android 全埋点解决方案

3.1 全埋点简述

全埋点,也叫无埋点、无码埋点、无痕埋点、自动埋点。全埋点是指无须 Android 应用程序开发工程师写代码或者只写少量的代码,就能预先自动收集用户的所有行为数据,然后就可以根据实际的业务分析需求从中筛选出所需行为数据并进行分析。

3.2 全埋点本质原理

自动拦截 => Android 对 View 的点击处理
自动插入 => 在编译阶段插入相应 Java 代码

自动插入的流程如下:

JavaCode --> .java --> .class --> .dex

3.3 全埋点解决方案

人都有惰性,都很懒,技术人员更懒,都想按一个回车就不想管事情。那用户行为的数据怎么拿到?每一个方法前后都要 添加代码,那开发不得累死。有没有更好更有效的办法?毕竟是人都想偷懒。

Activity生命周期状态监控

通过 Application 的 registerActivityLifecycleCallbacks 方法,ActivityLifecycleCallbacks 是 Appliaction 的一个内部接口,从 API 14 开始提供。在 Appliaction 中实现这个接口,便可以对所有 Activity 的生命周期进行监控。

在 onCreate 中调用如下代码。

registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

            }

            @Override
            public void onActivityStarted(Activity activity) {

            }

            @Override
            public void onActivityResumed(Activity activity) {
               Log.e("xmamiga","resumed          "+activity.getLocalClassName());

            }

            @Override
            public void onActivityPaused(Activity activity) {
                Log.e("xmamiga","paused          "+activity.getLocalClassName());
            }

            @Override
            public void onActivityStopped(Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
事件监控

全埋点采集的事件目前主要包括下面四种(事件名称前面的 $ 符号,是指该事件是预置事件,与之对应的是自定义事件):

  • $AppStart 事件:是指应用程序启动,同时包括冷启动和热启动场景。热启动也就是指应用程序从后台恢复。
  • $AppEnd 事件:是指应用程序退出,包括应用程序的正常退出、按 Home 键进入后台、应用程序被强杀、应用程序崩溃等场景。
  • $AppViewScreen 事件:是指应用程序页面浏览,对于 Android 应用程序来说,就是指切换 Activity 或 Fragment。
  • $AppClick 事件:是指应用程序控件点击,也即 View 被点击,比如点击 Button、ListView 等。

    APP 多进程如何判断?
    APP 奔溃被强杀怎么判断?

因为 Android 系统没有直接的方法判断 APP 处于前台还是后台,所以我们需要一些假定逻辑来实现这个功能。采用 ContentProvider 机制来解决多进程的问题,并通过数据库或者 SharedPreferences 来存储这些状态。
对于奔溃强杀问题,我们引入 Session 这个概念。

当一个页面退出了,如果 30 s 内没有新的页面打开那么我们认为应用进入后台了。
当一个页面显示了,如果和上一个页面退出的时间超过了 30 s 我们认为 APP 重新处于前台了。

具体方案:

注册 ActivityLifecycleCallbacks,监听 Activity 的生命周期。并采用 ContentProvider + SharedPreferences 的方式进行进程间数据共享,注册 ContentObserver 来监听跨进程间的数据通信。
页面退出的时候(onPause)启动一个倒计时 30 s ,如果 30 s 内没有新的界面显示触发 AppEnd 。如果有些页面那么,我们存储一个新的标记为来标记这个新页面(cp + sp)进行存储。然后通过 ContentObserver 监听新页面标记位的改变,取消定时器。如果 30 s 内没有新的页面(按 home 建 、退出、奔溃、强退等)我们会在下一次启动的时候补发这个 AppEnd 事件。
在下一次启动的时候,(onStart()),首先判断是否与上一个页面退出的时间间隔超过了 30 s ,如果没有超过 30 s 那么,那么无需补发 AppEnd,直接出发 AppScreen 事件。然后判断是否触发了 AppEnd,如果标志位是 true,那么出发 AppStart。反之不触发。如果超过了 30 s 那么就去看看是否已经触发了 AppEnd,如果没有则先补发 AppEnd,然后在 AppStart,最后 AppScreen。如果已经出发那么直接出发 AppStart,最后 AppScreeen。

ASM

Android 应用程序的打包流程有需要的自行查阅。Goodle 提供了 Transform API,用于 .class 文件打包成 .dex 文件之前实现想要的操作。ASM 是 Java 字节码操作和分析框架,可以通过 ASM 实现动态改变类或增强既有类的功能。如,在 click 方法后增加埋点记录逻辑。

动态代理
  • 代理 View.OnClickListener
  • 代理 Window.Callback
  • 代理 View.AccessibilityDelegate
静态代理
  • AspectJ 切面编程(AOP)
  • ASM
  • Javassist
  • APT 注解处理器

3.4 全埋点的缺点

目前的全埋点虽然已经基本可以实现所有页面、View 事件等数据的采集,但是还存在不少问题:

  • 关于业务数据的精准采集还比较困难,还不能准到跟手动代码埋点一样精确自如
  • 全埋点数据收集方式是通过全量采集后通过后台进行筛选,这就产生了大量的冗余数据,一定程度上浪费了一些终端与服务器的资源
  • 版本迭代可能会造成的不同版本之前页面事件、View 事件不一致,目前还没想到一个很简单的方式去管理

3.5 全埋点优化

完善配置化方案,通过后台的配置下发,精准打捞目标埋点,减少冗余数据,节省系统资源。开发圈选工具,使用圈选工具产品可以自己提取想要统计的 View 的 ViewId、PageId 等 Key 值。逐步增加全埋点覆盖面、改进兼容性。