前言:
本篇文章是《Android自动化埋点技术探索》的第一篇,主要介绍埋点的基本概念以及几种埋点技术实现方式的原理和差异
埋点基本概念及其意义
当一款Android应用上线后,开发人员或者运营人员,也可能是市场分析人员、管理人员、亦或者老板希望能收集一些用户操作的行为数据,比如用户在某个页面点击了多少次,应用中的某个控件被点击了多少次,在某个页面停留了多少时间等等,这些行为数据统一收集起来之后就可以交给数据分析师来进行数据的筛选、统计、分析、决策,而且以便于后期应用的方向性调整和技术功能调整。理论上来讲,这些收集统计的数据应该出自应用的PV(页面访问量)或UV(独立访客访问数)、或者统计应用中那些具体的页面最受欢迎、那些控件点击率最高or最低的场景。
举例,对于控件被点击多少次这个应用场景,开发人员的一般做法是在控件点击事件中加入Log代码,然后将此次的点击记录下来(自增),最终发送到服务端进行数据上传。页面的点击也是类似,需要在页面生命周期的开始加入Log代码。但如果业务逻辑复杂,页面众多,控件较多,那就要在许多地方插入这些数据上传的代码,因此实现方式和后期维护就成为了一个问题。通过在具体的功能实现的时候,去进行数据的存储和上传,这种技术可以简单理解为埋点 。那么有没有一种侵入方式低、集成起来简单就可以快速实现数据统计的埋点技术?
目前常见的前端埋点技术,一共有三大类:
- 在某个控件操作发生时通过预先写好的代码来发数据的代码埋点
- 通过可视化界面配置控件操作与事件发生关系的可视化埋点
- 收集所有数据之后,在后端筛选需要分析的对象的“无埋点”
关于这三种方案,下面做详细的解释说明:
可视化埋点之代码埋点
我相信这个可视化埋点技术是最简单也是最容易理解的一种实现方式,通过在需要统计的功能代码处,直接使用埋点SDK的代码来进行上传数据的技术,例如熟悉的友盟统计:
public void onResume() {
super.onResume();
//统计页面,仅有Activity的应用中SDK自动调用,不需要单独写。
//"SplashScreen"为页面名称,可自定义
MobclickAgent.onPageStart("SplashScreen");
//统计时长
MobclickAgent.onResume(this);
}
public void onPause() {
super.onPause();
//仅有Activity的应用中SDK自动调用,不需要单独写
//保证 onPageEnd 在onPause 之前调用,因为 onPause 中会保存信息。
//"SplashScreen"为页面名称,可自定义
MobclickAgent.onPageEnd("SplashScreen");
MobclickAgent.onPause(this);
}
简而言之这一类技术,是通过代码手动设置的可视化埋点功能代码,在APP或者界面初始化的时候,初始化第三方数据分析服务商的SDK,然后在某个事件发生时就调用SDK里面相应的数据发送接口发送数据。例如,想统计APP里面某个按钮的点击次数,则在APP的某个按钮被点击时,可以在这个按钮对应的 OnClick 函数里面调用SDK提供的数据发送接口来发送数据。
代码埋点的优点是一方面使用者控制精准,可以非常精确地选择什么时候发送数据;同时,使用者可以比较方便地设置自定义属性、自定义事件,传递比较丰富的数据到服务端。
当然,代码埋点的劣势在于:首先,埋点代价比较大,每一个控件的埋点都需要添加相应的代码,工作量会增大,而且限定了实现该功能的角色必须是技术人员才能完成;其次是功能更新的代价较大,每一次更新埋点方案,都须要改代码,更新完之后还要通过各个应用市场进行分发,并且总有一定数量的用户不喜欢更新APP,这样埋点代码也就得不到更新了;最后,就是所有前端埋点方案都会面临的数据传输时效性和可靠性的问题了,这个问题就只能通过在后端收集数据来解决了。
可视化埋点之页面配置埋点
通过页面配置来实现埋点的技术,其实是对第一种埋点技术-可视化代码埋点技术的一种升级,这种方式的实现过程如下:
在嵌入了埋点 SDK 的 APP 开启可视化埋点模式,SDK 会请求然后响应服务端的要求,APP内部定期(例如每秒)做一次截图,而 SDK 在为 App 截图的同时,会从Window 对象开始进行遍历页面的子view,得到当前视图下所有View对象的层级关系。
拿到View对象之后就可以获取一系列数据
服务端根据截屏和可视化信息来重新进行页面渲染,并且根据控件的类型,来识别哪些控件是可以增加可埋点的,并且将之标识出来。
当使用者在后台的截屏画面上点击了某个可埋点的控件时,后台会要求使用者做一些事件关联方面的配置,并且将配置信息进行保存和部署。
SDK 在启动或者例行轮询时拿到这些配置信息,则会通过设定的接口,为每个关联的控件添加的点击或者编辑行为的监听,并在回掉函数里面调用 SDK内部的接口发送相应事件的 track 信息来反馈埋点数据
至此,通过页面来进行配置埋点的技术大概实现过程就是这样。这种通过页面来进行配置的可视化埋点技术很好地解决了代码埋点的埋点代价较大以及更新代价大两个问题。但是,通过页面来进行配置的可视化技术能够覆盖的功能有限,目前并不是所有的控件操作都可以通过这种方案进行定制;同时,这种可视化埋点技术方案对自己设置属性有一定的缺陷,例如,一个界面上有一个文本框和一个按钮,通过可视化埋点设置点击按钮为一个“提交”事件时,并不能将文本框的内容作为事件的属性进行上传的,因此,对于可视化埋点这种方案,在上传事件时,就只能上传 SDK 自动收集的设备、地域、网络等默认属性,以及一些通过代码设置的全局公共属性了;最后,作为前端埋点的一种方案,可视化埋点也依然没有解决传输时效性和数据可靠性的问题。
自动化埋点
自动化埋点,也叫无埋点、无码埋点、全埋点。自动化埋点是指预先收集用户的所有行为数据,然后再根据实际分析需求从中提取行为数据。自动化埋点技术的整体解决思路,首先就是要找到那个被点击的View的点击处理逻辑(也叫原处理逻辑),然后利用一定的技术,对原处理逻辑进行“拦截”,或者在原处理逻辑的前面或者后面“插入”相应的埋点代码,从而达到自动埋点的效果。
那么如何做到自动“拦截” View 的原点击处理逻辑?一般是参考 Android 系统 View 点击事件处理机制来进行的。至于如何做到自动“插入”埋点代码,基本上都是参考编译器对 Java 代码的处理流程来进行的,即:JavaCode --> .java --> .class --> .dex
由于无埋点技术会涉及到apk的构建流程,这里先贡献一张APK的构建流程图:
在理解构建流程之前,首先要了解构建过程中各个工具的意义和主要功能,下面就对功能做详细的叙述:
名字 | 功能 | 详细介绍 |
---|---|---|
aapt | Android资源打包工具 | 可以查看,创建, 更新ZIP格式的文档附件(zip, jar, apk)。也可将资源文件编译成二进制文件。 |
aidl | Android接口描述语言转化为.java文件的工具 | 将android中我们用到的接口类描述语言,例如跨进程的aidl文件转化为java文件。 |
javac | Java Compiler | java语言编程编译器。全称javacompilation。javac工具读由java语言编写的类和接口的定义,并将它们编译成字节代码的class文件。javac 可以隐式编译一些没有在命令行中提及的源文件。 |
dex | 转化.class文件为Davik VM能识别的.dex文件 | 把所有的字节码文件转成Android DEX文件(classes.dex)。它是Android平台上可执行文件的类型。dx工具的主要工作是将Java字节码转成成Dalvik字节码、压缩常量池、消除冗余信息等。 |
apkbuilder | 生成apk包 | 将所有没有编译的资源(如images等)、编译过的资源和.dex文件都会打包到最终的.apk文件中。 |
jarsigner | jar文件的签名工具 | 工具利用密钥仓库中的信息来产生或校验 Java 存档 (JAR) 文件的数字签名 |
zipalign | 字节码对齐工具 | 它能够对打包的应用程序进行优化。 |
那么,APK构建的执行步骤分别就是:
步骤1.
使用aapt工具生成R.java文件
将Resource文件(就是工程中res中的文件)、Assets文件(相当于另外一种资源,这种资源Android系统并不像对res中的文件那样优化它)、AndroidManifest.xml文件(包名就是从这里读取的,因为生成R.java文件需要包名)、Android基础类库(Android.jar文件)打包成资源(一般在Android工程的bin目录可以看到一个叫resources.ap_的文件就是它了)、R.java文件(在gen目录中)。
步骤 2.
处理AIDL文件,生成对应的.java文件
将源码文件、aidl文件、framework.aidl等应用到aidl的描述语言文件转化为java文件。
步骤 3.
编译Java文件,生成对应的.class文件
将源码文件(包括R.java和AIDL生成的.java文件)、库文件(.jar文件)编译为.class文件。
步骤 4.
把.class文件转化成Davik VM支持的.dex文件
将任何第三方的libraries和.class文件都会被转换成.dex文件。dex工具生成可供Android系统Dalvik虚拟机执行的classes.dex文件。
步骤 5.
打包生成APK文件
所有没有编译的资源(如images等)、编译过的资源和.dex文件都会被apkbuilder工具打包到最终的.apk文件中。
步骤 6.
对APK文件进行签名
一旦APK文件生成,它必须被签名才能被安装在设备上。在开发过程中,主要用到的就是两种签名的keystore。一种是用于调试的debug.keystore,它主要用于调试,在Eclipse或者Android Studio中直接run以后跑在手机上的就是使用的debug.keystore。另一种就是用于发布正式版本的keystore。
步骤 7.
对签名后的APK文件进行对齐处理
如果发布的apk是正式版的话,就必须用到的工具zipalign对APK进行对齐处理。对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用。
因此,无埋点技术实现的基本原理,就是利用某些技术对某些方法(View 被点击时的处理逻辑)进行代理(或者叫 Hook),或者叫在指定的时机 插入代码。
那么,如何进行代理,又该如何在指定的时机进行 插入代码?
按照 “在什么时候去代理或者插入代码”这个条件来区分的话,Android无埋点技术可以大致分为下面两种方式 :
静态代理
所谓静态代理,就是指通过 Gradle Plugin 在 编译期间 “插入”或者修改代码(.class 文 件)。比如 AspectJ、 ASM、javassist、AST 等方案均是这种方式来实现在编译期间“插入”或者修改代码
动态代理
所谓动态代理,就是指在 代码运行 的时候去进行代理。比如开发中比较常见的代理 View.OnClickListener、Window.Call-back、以及View.AccessibilityDelegate 等方案均是这种方式。
总之:不同的方案,其处理和运行效率也有很大的差异,对 App的浸入程度以及对 App 的整体性能的影响也各不相同。而且Android系统的不断升级,不管是 Android 系统本身,还是与 Android App 开发相关的组件和技术,都在与时俱进。因此针对不同的应用场景,要具体问题具体分析。
本篇文章是《Android自动化埋点技术探索》的第一篇,主要介绍埋点的基本概念与几种实现方式差异进行对比,接下来的文章主要对自动化埋点技术这一种实现方式进行分析和探索。
文章部分内容选自:神策数据用户行为洞察研究院《安卓全埋点技术白皮书》,感谢技术分享!
如果这篇文章对您有开发or学习上的些许帮助,希望各位看官留下宝贵的star,谢谢。
Ps:著作权归作者所有,转载请注明作者, 商业转载请联系作者获得授权,非商业转载请注明出处(开头或结尾请添加转载出处,添加原文url地址),文章请勿滥用,也希望大家尊重笔者的劳动成果!