1 行业内全埋点技术方案调研
调研文章链接:
- 网易云音乐Android 自动埋点实践
- 网易HubbleData之Android无埋点实践
- 58无埋点数据采集技术在Android端实践
- 51信用卡Android自动埋点实践
- 美团无埋点方案 - Gradle Plugin 的方式,在编译期间修改 class
1.1 数据处理流程
- 数据采集
- 数据上报
- 数据存储
- 数据分析
- 数据展示
1.2 现状、痛点
主要使用代码埋点的方式进行数据采集,所谓代码埋点指的是在某个事件发生时通过预先写好的代码来发送数据,基于预先编码实现的代码埋点,其优点是:控制精准、采集灵活性强,可以自由的选择什么时候发送什么样的数据;但缺点也同样十分明显,开发、测试成本高,对于客户端而言需要等待发版才能修改线上的埋点。
1.3 业内情况
全埋点,也可称为无埋点或者无痕埋点,即在端上自动采集并上报尽可能多的数据,在计算时筛选出可用的数据。其优点是:很大程度上减少开发、测试的重复劳动,数据可以回溯并且全面;缺点是:采集信息不够灵活,并且数据量大。
可视化埋点,则是通过可视化工具选择需要收集的埋点数据,下发配置给客户端,从而解析配置采集相应埋点的方式。其优点是:很大程度上减少开发、测试的重复劳动,数据量可控,可以在线上动态的进行埋点配置,无需等待App发版;其缺点同样是采集信息不够灵活,并且无法解决数据回溯的问题。
1.4 全埋点
针对页面、控件等元素需要生成其ID,该ID需尽量具备『唯一性』和『稳定性』。『唯一性』非常好理解,因为对于任意元素而言,其ID应该是与其他所有元素都不同的,这样我们才能根据ID唯一标识出那个我们想要的元素,采集上来的数据才是准确的,不重复的。而『稳定性』则是说,元素的ID应尽量不受版本的变动而改变,这样后期关联业务含义的操作才会更加便捷。
1.4.1 页面ID规则
- 类名(51信用卡方案)
Activity,ID规则为ActivityClassName|额外参数
Fragment,ID规则为ActivityClassName[FragmentClassName]|额外参数
- 页面class全路径(58同城)
1.4.2 控件ID规则
-
页面ID:控件路径
(51信用卡、网易、58同城)
从点击控件开始遍历控件树直至找到根节点确定控件路径
FrameLayout[0]/LinearLayout[1]/Button[0],优先使用Resource ID作为控件ID,若Resource ID不存在,则降级使用控件路径作为控件ID。
1.4.3 代码插桩方式
- Javassist + Transform(51信用卡方案)
- ASM + Transform(58同城、网易云音乐)
1.4.4 埋点方式
- 利用Gradle插件自动注入埋点代码为主,并辅以手动埋点进行数据定制化补全的技术方案。(58同城)
H5的日志通过JSBridge调用Native,由Native统一向后端发送日志信息。
2 Android全埋点实现方案总览
2.1 AppViewScreen(页面浏览)
注册Application.ActivityLifecycleCallbacks
监听,在Activity的生命周期onResume
中触发事件
2.2 AppStart、AppEnd(当前应用处于前台还是后台)
注册Application.ActivityLifecycleCallbacks
监听
理解前台还是后台:
对于一个应用,打开App的一个页面则在前台,触发AppStart事件,触发当它的页面退出了,如果在30秒之内没有新的页面打开,则认为应用处于后台,触发AppEnd事件
在页面退出的时候(即onPause生命周期函数),启动一个30秒倒计时,如果20秒之内没有新的页面进来(或显示),则触发 AppEnd 事件。如果有新的页面进来,则存储一个标记位来标记已有新的页面进来。这里需要注意的是,如果你的 Activity 之间可能存在跨进程,所以标记位需要实现进程间共享,可以通过 ContentProvider + SharedPreferences 来进行存储,通过ContentObserver 监听新页面进来的标记位改变,从而取消上个页面退出时启动的倒计时。如果30s之内没有新的页面进来,如用户按Home键或返回键退出应用程序、应用程序发生崩溃、应用程序被强杀,则会触发 AppEnd 事件,但应用程序发生崩溃、应用程序被强杀会导致我们无法及时触发 AppEnd 事件,只能在用户下次进行启动时补发。如果用户后面再也不启动应用程序,则会导致 AppEnd 事件丢失。
在页面启动的时候(即 onStart 生命周期函数),我们需要判断一下与上个页面退出的时间间隔是否超过了30s,如果没有超过30s,则直接触发 AppViewScreen 事件。如果已经超过了30s,则需要判断之前是否已经触发了 AppEnd 事件,如果没有触发,则先触发 AppEnd 事件。然后再触发 AppStart 事件和 AppViewScreen 事件。
2.3 AppClick(控件点击)
方案 | 原理 | 优缺点 |
---|---|---|
代理View.OnClickListener/View.OnTouchListener | 通过反射获取点击View的mOnClickListener/mOnTouchListener对象并用自定义Listener代理 | 使用反射,效率低,有兼容风险;无法采集游离于Activity之上的控件(如Dialog等)的点击 |
代理Window.Callback | 用自定义的Window.Callback代理系统的 | 每次点击都需要去遍历RootView,效率低,影响性能;无法采集游离于Activity之上的控件(如Dialog等)的点击 |
代理View.AccessibilityDelegate | 用自定义的View.AccessibilityDelegate代理系统的 | 使用反射,效率低,有兼容风险;无法采集游离于Activity之上的控件(如Dialog等)的点击;辅助功能需用户手动开启 |
透明层 | 在所有Activity的最上层添加透明View重写onTouchEvent拿到View代理mOnClickListener | 每次点击都需要去遍历RootView,效率低,影响性能;无法采集游离于Activity之上的控件(如Dialog等)的点击 |
AspectJ | 编译期插入埋点代码 | 无法织入三方库;兼容性问题;切点依赖编程语言;无法兼容Lambda语法(前两个缺点已经被aspectjx库解决) |
ASM | 编译期修改字节码插入埋点代码 | 优点:小而快,操作灵活;缺点:需要对字节码有一定的了解 |
javassist | 编译期修改字节码插入埋点代码 | 优点:源代码级API调用,不需要了解字节码;缺点:使用到了反射,性能低于ASM |
AST | 编译期操作AST修改源代码 | APT无法扫描其他module;不支持Lambda;com.sun.tools.javac.tree的API理解难度大 |
网友评论