cover_image

无埋点方案的探索与实践

无线研发团队 北京顺丰同城科技技术团队
2024年05月30日 07:40
图片

点击上方蓝字关注我们




01
简述
       最早提出大数据概念的著名CEO麦肯锡称:“数据已渗透到每个行业领域,成为重要的生产因素。海量数据预示着新一波生产率增长和消费者盈余浪潮的到来”。
       万物互联的大数据时代,提起埋点统计相信大家并不会太陌生,在大数据处理分析流程中,埋点扮演了数据采集的关键角色。移动端产品数据埋点也不例外,它有着不可忽视的重要性和潜在力量,如在项目代码中,对某些功能操作手动写入埋点上报,可实现洞悉用户的使用特征,从而达到辅助优化产品高质量迭代,监控运营状态的目的。而“无埋点方案”则可帮助开发者,实现全操作自动埋点代码注入。
       本文将会从以下章节详细说明,无埋点方案思维的建立的必要性,替代原始方案的关键优势,移动端无埋点采集实现机制,采集数据的处理以及本方案带来的收益。

02
背景
市场现存的三方埋点服务有很多,大多都是通过对某个产品下某个具体业务场景进行单独埋点,如:统计消息中心页面的曝光时长,统计发送按钮的点击次数。这种埋点方式存在的痛点也是不言而喻的:
  • 低效率:想要统计了解每个产品的使用事件,业务方需要为每个产品的事件集合单独定制一套专属埋点。提交的埋点需求给多方开发团队,通过手动代码埋点、发版、采集、上报、统计分析才能看到数据,耗时费力。
  • 低容错率:对于不熟悉业务代码的开发者来说,相比自动化,手动插入埋点无疑会有代码操作错误的产生,错误的海量数据对于市场统计分析来说是相当恐怖的事。
  • 扩展性差:APP市场业务多且迭代速度快,当新的需求产生,一批新的手动埋点人力必然会随之形成。
       无埋点方案正是为了解决上述问题而生,使用一套通用化解决方案替代手动埋点的老方式,做到让所有APP开发者仅需要一次代码规则构造,就可将获取的前端行为信息展示在运营端。相比三方,此方案还具备其独特优势:
  • 强定制化:根据公司内部习惯生成可定制化埋点事件上报样式、看板规则。
  • 易于管理:埋点全流程更加透明,且部署内部服务,拥有底层查改权限。
  • 高安全性:数据日志上报给内部服务器,有效保证了用户隐私泄露。

03
目标
图片
       无埋点通用化解决方案面向的过程包括了:移动端APP事件日志采集上报、后端日志数据库存储、前端网页数据过滤展示,各环节均有其要思考的细节,本文只针对涉及到Android端的内容进行目标设计,这其中涵盖了:
  • 明确通用化采集事件:用户在APP中操作的哪些事件是需要采集的,如何制定通用化事件分类。
  • 代码无埋点设计策略:如何在Android项目中对各事件进行无埋点设计,是否做到无业务代码入侵。
  • 明确前后端通讯规则:事件采集格式是怎样的,上报的时机是怎样的,上报的方式是批量还是单条。

04
Android移动客户端数据采集实现

1、明确通用化采集事件

       通过以往大量手动埋点的类型总结来看,用户的行为习惯无非有这些种类:

  • 页面切换事件(to):页面A:页面A中文名称(切换前)自动唯一标识※页面B:页面B中文名称(切换后)自动唯一标识

  • APP启动/结束事件(start/end):启动、退到后台、恢复前台、结束

  • 组件操作事件(click):对于被操作控件的点击/长按操作类型※所在列表中的位置※所在页面唯一标识※控件展示文案

  • 弹窗对话框事件(show):当前页面唯一标识※弹窗固定标识※弹窗包含的一组文本

  • 崩溃事件(crash):崩溃日志文本

  • 自定义事件(set):自定义内容

2、各事件采集原理

       为使Android开发者更加便捷地接入上述无埋点事件,此次设计是将“无埋点自动代码采集上报”封装成SDK的形式展开,达到一次初始化传入Application到SDK便可快速接入项目的目的,它的优势不必过度赘述,在SDK设计中有两大重点原则:

  • 纯净:无业务层代码侵入。如组件操作Click事件,以往的策略都是在业务层组件的View.setOnClickListener的代码中对组件的点击事件进行监听,当用户点击了组件,则执行手动埋点代码,而本方案的做法则是,找到一种方法对所有组件的View.setOnClickListener代码块进行拦截,在原处理逻辑执行前后进行自动埋点代码插入。

  • 高效:尽可能在代码编译期插入代码而非运行期。众所周知,Java编译器运行Android应用程序会通过以下几个阶段,JavaCode -> .java ->.class -> .dex -> Runtime,其中可以通过使用Gradle插件在编译(Compile)期间对class文件做插入或修改(静态代理)。也可以通过在的代码运行(Runtime)期间对View.setOnClickListener进行代理(动态代理)。对比来说,静态代理的运行效率要远优于动态代理,原因是动态代理的埋点动作是在运行期间进行操作,会对业务运行性能造成干扰。

3、页面切换事件/APP启动/结束事件

       页面切换浏览就是指用户在不同的Activity或者Fragment组件中进行切换,对于这两个组件都有其对应的生命周期监听器,对于Fragment可以有属于自己的栈管理器,如何监听到这些组件的展示回调是实现无埋点代码插入时机的关键。

       Activity生命周期的监听大家首先想到的就是在BaseActivity中的OnResume生命周期中拿到页面ID做埋点逻辑,让所有业务层Activity都继承这个BaseActivity,这种方案是可行的,最大的痛点就是无法控制让三方库中的Activity组件库也继承BaseActivity。而且这种方案违背了“纯净”原则,入侵了业务代码。本方案采用Application.ActivityLifecycleCallbacks形式对各Activity组件进行监听,它是Android 4.0开始提供,通过注册Application便可收到所有Activity的所有生命周期回调,代码如下,这里还能通过计算Activity的数量实现监听APP启动/结束事件(代码如下所示):

图片

       Fragment生命周期的监听也可以模仿这种方式,通过registerFragmentLifecycleCallbacks注册FragmentLifecycleCallbacks从而实现各Fragment生命周期的回调,这种形式的痛点在于使用add来添加Fragment时,FragmentLifecycleCallbacks会收不到系统的生命周期回调函数。本方案采用的另外一种形式则是监听回退栈的变化,在Application.ActivityLifecycleCallbacks的onActivityCreated函数中先对其内部的各大Activity的Fragment回退栈注册监听器(代码如下所示)。

图片

       而后重写FragmentManager.OnBackStackChangedListener,做逻辑埋点。

图片

       为了便于前端运营人员更易理解页面ID含义,可使用一些手段上报页面ID语义化翻译给后端做筛选处理。可选方案有手动建立ID翻译映射关系表格上报;页面切换埋点采集时自动上报。这两种方案都需要Android开发者为对应的页面起名字,前者需要前后端规定建表格式规则,手动做映射复制工程,流程自动化能力差,而后者对于后端具备明显优势,因为不用再次对表格中的数据进行映射和导入处理流程。本方案采用第二种页面注解语义化采集方案,在Android项目代码Activity、Fragment组件类上标记注解中文名即可(代码如下所示)。

图片
4、组件操作事件

       方案一:同样采用Application.ActivityLifecycleCallbacks形式对各Activity组件进行监听,在onActivityResumed回调方法中通过activity.findViewById(R.id.content),就可以拿到整个布局中的所有View(通过判断是否为ViewGroup类型进行遍历),找到目标View后进行埋点代码代理插入操作。首先通过反射的手段获取到点击事件对象原生业务逻辑,进行判断若类型是原生的View.setOnClickListener则进行自定义包装成WrapperOnClickListener,在执行原生OnClick之后插入埋点逻辑。这种方案的缺点是反射效率低,对APP本体性能有影响,再者伴有兼容性问题,反射取点击对象操作时对API有版本要求。

       方案二:Android打包流程中,在打包成.dex文件之前可利用Transform API拿到.class文件,Google允许使用这套API以Gradle插件形式,实现对打包成.dex 文件之前的编译过程中操作字节码文件.class,然后再利用ASM字节码操作框架对其进行加载解析,它能动态生成类或者增强既有类的功能,对于点击事件操作事件,即找到特定的clickListener方法对其进行埋点代码插入修改,这就是所谓的“字节码插桩”,保存修改后可顺利通过编译规则打包成应用程序并运行。因为是在编译期进行代码插入,对于程序运行时的性能是没有影响的,并且不受API版本等兼容性问题影响,流程如图:

图片
PS:实现流程本文只贴出关键代码,完整示例请参见:https://github.com/wangzhzh/AutoTrackAppClick6

第一步:定义出要使用ASM插入到字节码文件中的埋点代码。

图片

第二步:新建GradlePlugin,并新建一个Transform类,其中在transform方法中要实现遍历目录中的每一个.class文件,修改后并保存。

图片

第三步:遍历到.class文件后,利用ASM中的ClassVisitor进行修改符合特定条件的方法。

图片
5、弹窗对话框事件

       可见组件操作事件代码插桩方案二,针对Dialog字节码文件识别条件如下,过程不再赘述。

图片

6、崩溃事件上报事件

       崩溃信息对于Android开发者跟踪错误日志是尤为必要的,譬如可了解分析到线上未适配的新机型,整理崩溃CASE产生条件等,有助于提升完善产品质量。本方案采用Crash拦截立即上报的方式,保证服务端第一时间拿到最新的Crash信息,若上报未成功,则下次开启APP后轮训上报时机重试。

图片

7、SDK封装及前后端通讯规则

       为使Android开发者更高效的接入无埋点方案,根据上述埋点策略,可将Gradle插件及埋点代码抽象出来封装成SDK。本SDK具备了日志采集、缓存、上报能力,流程如下图所示,采集到的日志先会放到内存缓存,当轮询时间已到便会触发上报网络任务,若上传成功则会自动删除数据,若失败则将数据存入外存,等待下一次上报时机读取内/外存再次重试。

图片
其特点在于:
  • 支持内存 + 硬盘缓存,防止采集事件数据意外丢失;缓解内存压力,防止OOM导致程序Crash。 
  • 替代单条日志采集完就上报的形式,采用轮询时间批量日志上报,能节省网络开销及缓解服务器压力。 
  • 可配置轮询上报时间、内存缓存大小上限、外存缓存文件大小上限、最小上报时间间隔,保证具备项目合理化定制的能力。 
  • 上报前包含了必备的公共参数(时间+项目ID+版本号+用户ID),更加便于服务端对日志进行筛选分类管理。

8、数据处理

       得益于高度规范化埋点规则,数据采集、处理及可视化均已告别低效的手动流程,统一的采集服务,流批一体化清洗流程和多维度分析看板使得客户端埋点接入即可实时追踪用户行为,h+1获悉新增App分析数据。

  • 技术架构

图片

  • 数据采集:统一采集服务集群,可接收Native/H5/Wechat全端上报,支持Native Post批量上传,支持H5 Img方式上报。
  • 数据处理:埋点的核心诉求不是数据,而是基于数据分析的结论或者决策建议。通常情况下,埋点分析可化分为两种:

     行为追踪,即获悉谁(who),何时(when),何地(where),做了什么(what)以及如果做到(how)。

     趋势分析,通过个体的行为统计,获悉各用户群体行为趋势,以辅助或佐证决策。

    针对上述两种分析场景,对数据处理要求也不尽相同,行为追踪追求数据时效性,如实时推荐,用户操作过程中,即刻通过行为样本分析进行个性化推荐。而趋势分析,则寻求通过复杂计算逻辑挖掘数据最为深层的价值。所以针对不同的业务诉求在数据处理上也采用了Lambda流批一体的方式:实时加工+离线分析。

  • 批一体:通过离线补偿方式,确保实时数据最一致性,准确性。

     实时加工:通过Flink集群实时逐条加工埋点信息,实现每日百万级埋点上线即可实时追踪。
    ◇ 离线分析:通过批处理方式,将埋点与其他业务数据整合,进行维度建模,产生多维度分析结果。 
  • 可视化:数据可视化可以大大提升数据化决策效率,而往往因面向用户群体不同,决策场景不同会产生难以兼容的差异,所以数据团队通过埋点基础表与Muse看板,兼顾各分析场景诉求。

    ◇ 通用看板:根据通用埋点信息,数据团队产出了用户行为跟踪,页面分析,用户画像看板,通过多维度关联下钻方式,满足个体行为分析,趋势分析,用户群体分析等场景诉求。

    个性分析:针对各业务个性化诉求,可通过在线查询集群结合报表系统,以拖拉拽方式快速由底层数据进行可视化分析。


05
收益

       无埋点方案通过与大数据后端规定的事件约束,制定了通用化埋点事件分类,支持了移动客户端项目中无埋点日志采集上报能力,并且将其抽象包装成库组件,使开发者高效接入,真正做到了无业务代码入侵式埋点并自动完成采集、缓存、上报日志流程。它的意义不仅为科技公司打造了属于自己的无埋点生态环境,更在于便于监控和洞悉产品运营状态及使用特征,从而辅助为客户优化打造更高质量的产品迭代。

图片

THE END

继续滑动看下一个
北京顺丰同城科技技术团队
向上滑动看下一个