1.4.2 声明式埋点
声明式埋点的思路是将埋点代码与具体的交互和业务逻辑解耦,开发者只用关心需要埋点的控件,并且为这些控件声明需要的埋点数据即可,从而降低埋点的成本。
在 Android 中,自定义了常用的 UI 控件,如 TextView、 LinearLayout、 ListView、ViewPager 等,重写了事件响应方法,在这些方法内部自动填写埋点代码。重写控件的好处在于,可以拦截更多的事件,执行效率高且运行稳定。但其弊端也非常明显——移植成本比较高。
为了解决这个问题,可借鉴 Android 7.0 支持库的思路,即通过 AppCompatDelegate 代理自动替换 UI 控件,代码如下:
1. public class GAAppCompatDelegateV14 extends AppCompatDelegateImplV14 {
2. @Override
3. View callActivityOnCreateView(View parent, String name, Contextcontext,
AttributeSet attrs) {
4. switch (name) {
5. case "TextView":
6. return new NovaTextView(context, attrs);
7. }
8. return super.callActivityOnCreateView(parent, name, context, attrs);
9. }
10. }
这样,开发者只需要在自己的 Activity 基类中重写 getDelegate 方法,将方法的返回值替换为修改过的 AppCompatDelegate,这样就可以实现自动替换 UI 控件了:
1. @Override
2. public AppCompatDelegate getDelegate() {
3. if (mDelegate == null) {Android 高效进阶:从数据到 AI
4. mDelegate = GAAppCompatUtil.create(this, this);
5. }
6. return mDelegate;
7. }
如果引用的第三方库中重写了 UI 控件,则上述方法是不生效的,也就是说需要一种替换UI 控件类的父类方法。但是在运行时,没有找到可行的替换 UI 控件类的父类方法,因此建议尝试在编译时修改父类,并开发一个 Gradle 插件。事实上,这样做并不存在运行时效率的问题,只会牺牲一些编译速度。这样开发者只需要运行这个插件,就可以实现自动将 UI 控件的父类替换为重写的 UI 控件了。
在采用了声明式埋点后,只需要在控件初始化时声明一下需要的埋点即可,不必再侵入程序的各种响应函数,这样会降低埋点难度:
1. GAHelper.bindClick(view, bid, lab);
声明式埋点能够替代所有的代码埋点,并且能解决早期遇到的移植成本高等问题。但是其本质上还是一种代码埋点,只是埋点的代码量减少了,并且不再侵入业务逻辑了。如果要满足动态部署与修复埋点的需求,则需要彻底重构前端硬编码的埋点代码。
1.4.3 无痕埋点
声明式埋点之所以还需要硬编码,主要有两个原因:第一是需要声明埋点控件的唯一事件标识;第二是有的业务字段需要在前端埋点时携带,而这些字段是在运行时才可获知的值。
对于第一点,可以尝试在前后端使用一致的规则自动生成事件标识,这样后端就可以配置前端的埋点行为,从而做到自动化埋点。对于第二点,可以尝试通过某种方式将业务数据自动与埋点数据关联,这种关联可以发生在前端,也可以发生在后端。
数据埋点与采集是进行数据分析的基础。在第三方统计平台普遍提供的前端埋点解决方案中,手动埋点是最基础且最成熟的方式,但却因其技术门槛高、操作复杂、周期长等弊端为广大数据分析人员及技术人员所诟病。而解决这些问题正是后来兴起的无痕埋点技术的优势所在。
无痕埋点技术早在 2013 年就被 Heap Analytics 等公司应用在了数据分析领域,但在国内直到 2016 年才开始被广泛关注,并同时出现了全埋点等技术描述。
事实上,无论是无痕埋点还是全埋点,它们的核心技术基础是一致的。它们都是通过基础代码在所有页面及页面路径上的可交互事件元素上放置监听器来实现数据采集的。所以,与其说它们不需要埋点,还不如说代码帮开发者完成了处处埋点的烦琐工作。
早期有人区分两者的依据是,全埋点会将所有数据全部采集回收,而无痕埋点只会回收通过可视化界面配置的事件的数据。但事实上,随着相关功能弥合度逐渐提高,这种以功能进行区分的界限逐步消除,所谓的差异也就不准确了。因此,当下更愿意把无痕埋点或全埋点当作一种营销包装方式。
下面就来详细阐述无痕埋点。
1.问题的引入
在开发过程中,不可避免要对事件进行统计,比如,对某个界面启动次数的统计,或者对某个按钮点击次数的统计,一般大公司都会有自己的统计 SDK,而其他公司则大部分会选择友盟统计等第三方平台。但是他们都需要在代码里面的每一个事件产生的地方插入统计代码,如某个事件触发 onClick 事件,那么就在 view.setOnClickListener()的 onClick 方法里写入MobclickAgent.onEvent(MyApp.getInstance(),"login_click")。这时候会有人想,有没有一种办法不用这么麻烦呢?
通过上面的分析,有过 AOP 开发经验的读者就会想到,这就是典型的 AOP 应用场景。接下来介绍具体的解决方案。
2.解决方案
在第三方统计中,每一个事件都会对应一个 ID,而这个 ID 可以由开发人员自己定义,对应的 ID 会有事件描述,这样就形成了一个表格,将这个表格上传到统计平台。当需要统计某个事件的时候,只需将事件的 ID 上报即可,而后台就会记录对应的 ID 事件统计。通过观察,大部分的事件统计都是 onClick 事件。
接下来需要解决几个问题。
问题一:如何在对应的事件上动态注入代码。
问题二:如何动态地生成一个事件的唯一 ID。
问题三:如何将 ID 和事件描述对应上。
方案一:在 AOP 里面有一个 Javassist 库,可以很便利地动态修改 class 文件。在将 java 文件编译成 class 文件之后,可以找到所有实现 android.view.View.onClickListener 的类,包括匿名类,然后在它们的 onClick(View v)中