写点什么

太恐怖了!移动开发 APP- 可视化埋点技术原理竟然是这样的?!

  • 2021 年 11 月 07 日
  • 本文字数:2135 字

    阅读完需:约 7 分钟

原文:个推大数据

一、背景

运营者能够对用户行为进行分析的前提,是对大量数据的掌握。在以往,这个数据通常是由开发者在控件点击、页面等事件中,一行行地编写埋点代码来完成数据收集的。然而传统的操作模式每当升级改版时,开发和测试人员就需要重复不断对代码进行更新,整个流程耗时长,无法满足业务的需求。


为帮助开发者解决这一痛点,个推应用统计“个数”推出“可视化埋点”这一技术来更高效地实现这个这一过程。“个数”的可视化埋点灵活、方便,开发者不需对数据追踪点添加任何代码,只需要连接管理台并圈选页面中需要埋点的元素,即可添加随时生效的界面追踪点。


本文将结合个数实践经验,对可视化埋点中的两大关键技术点即控件唯一标识和事件采集进行分析并提供解决方案。


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码

二、可视化埋点关键技术点

可视化埋点的难点,或者说核心就是如何在开发者不编写任何代码的情况下,SDK 如何确定任意一个控件在该应用内的唯一性,以及如何监听控件的点击和页面的切换。


标识


为了防止不同页面中的控件标识重复出现,控件的唯一标识一般由页面标识加上控件标识生成。


页面标识生成


页面标识可以直接使用页面的名称,即 Activity name。其获取方式比较多,这里介绍一种比较通用的方法,即通过注册 Application.ActivityLifecycleCallbacks ,开发者可以在以下生命周期的回调中,轻松地拿到当前的 Activity 对象。此方法适用于一个 Activity 并无 Fragment 存在的情形。


代码详见下图:



获取方式也是比较多,不过较于 Activity 的获取会相对麻烦一些,因为系统没有直接提供 API ,因而需要稍微转个弯:通过 Gradle 插桩的方式,获取 Fragment 的生命周期,以及 Fragment 实例对象:


如果该应用的页面存在一个 Activity 中嵌套多个 Fragment 的情况,单单一个 Activity name 则可能无法精准地定位到某个页面,因而还需要加上 Fragment 的名称。Fragment 的获取可以通过 Gradle 插桩法来实现,即根据 Fragment 的生命周期来获取 Fragment 实例对象。



1.2 控件标识生成


理想的情况下页面中的每个控件都有属于自己的唯一 id,SDK 直接获取控件的 id 当做控件标识即可。但现实情况却是,一个页面中往往存在多个相同 id 的控件,或者是没有 id 的控件,比如 Listview 的 item ,开发者不可能给 listview 的每个 item 设置不同的 id。


因此需要转变一下思路。我们可以从控件路径这个除 id 外比较独特的性质着手来生成控件标识。开发者可以通过给控件的路径加上控件角标的结构方式,生成控件的唯一标识。下图是 Github 上一个仿 B 站的应用。我们对这个应用进行一下控件树分析。首先我们使用 Android Studio 自带的 UI Automator Viewer 工具查看该页面的布局结构:



接下来,我们可以从 Application.ActivityLifecycleCallbacks 的回调中拿到 Activity 实例,再使用 activity.getWindow().getDecorView().getRootView() 方法来获取当前页面的控件树。


例如图中的文字控件是 TextView,且无兄弟布局,则可以标记为 TextView[0] 。它的父布局是 LinearLayout 且排在兄弟布局中的第二位,那么就可以写成是 LinearLayout[1],然后使用自己定义的符号拼接,像是 LinearLayout[1]/TextView[0] 。之后以此类推、循环遍历、层层递进,将所有经过的控件以及它们的下标都拼接起来,组成控件在该页面中的唯一标识。


对于一些可复用的 View ,我们则需要采取一些特殊处理。例如对于 RecyclerView、ListView、 ViewPager 等复用控件,我们都需要采取不同的处理方式,去获取当前 View 在该控件中的具体下标。如果没有进行特殊处理,则会导致子控件错位,数据统计不准确。


采集


在以往的处理中,如果需要知道一个按钮的点击次数,开发者就要在该控件的 click 事件中加入对应的打点代码。这种重复劳作,无疑增加了开发者的开发负担。对此,我们可以采用动态代理方式或 Gradle 插桩方式来改善这个问题。


动态代理方式


使用安卓自带的辅助功能 View.AccessibilityDelegate 。前文提到当页面变化时,我们可以通过 Application.ActivityLifecycleCallbacks 获取到 Activity 的实例对象,接着根据 activity.getWindow().getDecorView().getRootView() 来获取到控件树。由于控件树可能会实时发生变化,我们则需要通过 ViewTreeObserver.OnGlobalLayoutListener 的方法监听视图变化,从而在该回调中拿到变化的控件。接着我们 要根据递归判断该控件是否为 ViewGroup、是否可以点击、是否能够显示等,继而给符合条件的 View 设置 sendAccessibilityEvent();此外,我们还要在继承了 View.AccessibilityDelegate 的定义类中,对以下这些方法添加 SDK 的代理:



当对应的控件被点击时,系统就会自动调用设置过代理的方法,存储或者上报对应数据。



Gradle 插桩的方式


Android Gradle 工具在 1.5.0 版本后提供了 Transfrom API , 该 API 允许第三方 Plugin 在打包 dex 文件之前的编译过程中操作 .class 文件。在编译期,开发者可以通过 onClick、onItemClick 等方法(详见下图)进行监听,这相当于是正则匹配。


当上述监听的方法被编译的时候,就可以将埋点的代理操作插入这些方法中,实现自动化埋点的流程。网上相关流程也是非常详细,有兴趣的可以自行搜索学习。

评论

发布
暂无评论
太恐怖了!移动开发APP-可视化埋点技术原理竟然是这样的?!