揭秘!如何用Flutter设计一个100%准确的埋点框架?

简介: 用户行为埋点是用来记录用户在操作时的一系列行为,也是业务做判断的核心数据依据,如果缺失或者不准确将会给业务带来不可恢复的损失。

image

阿里妹导读:用户行为埋点是用来记录用户在操作时的一系列行为,也是业务做判断的核心数据依据,如果缺失或者不准确将会给业务带来不可恢复的损失。闲鱼将业务代码从Native迁移到Flutter上过程中,发现原先Native体系上的埋点方案无法应用在Flutter体系之上。而如果只把业务功能迁移过来就上线,是极其不负责任的。因此,经过不断探索,闲鱼技术团队沉淀了一套Flutter上的高准确率的用户行为埋点方案,今天由工程师兰昊来和大家分享一下。

用户行为埋点定位

先来讲讲在我们这里是如何定义用户行为埋点的。在如下用户时间轴上,用户进入A页面后,看到了按钮X,然后点击了这个按钮,随即打开了新的页面B。

image

这个时间轴上有如下5个埋点事件发生:

  • 进入A页面。A页面首帧渲染完毕,并获得了焦点。
  • 曝光坑位X。按钮X处于手机屏幕内,且停留一段时间,让用户可见可触摸。
  • 点击坑位X。用户对按钮X的内容很感兴趣,于是点击了它。按钮X响应点击,然后需要打开一个新页面。
  • 离开A页面。A页面失去焦点。
  • 进入B页面。B页面首帧渲染完毕,并获得焦点。

在这里,打埋点最重要的是时机,即在什么时机下的事件中触发什么埋点,下面来看看闲鱼在Flutter上的实现方案。

实现方案

进入/离开页面

在Native原生开发中,Android端是监听Activity的onResume和onPause事件来做为页面的进入和离开事件,同理iOS端是监听UIViewController的viewWillAppear和viewDidDisappear事件来做为页面的进入和离开事件。同时整个页面栈是由Android和iOS操作系统来维护。

在Flutter中,Android和iOS端分别是用FlutterActivity和FlutterViewController来做为容器承载Flutter的页面,通过这个容器可以在一个Native的页面内来进行Flutter页面的切换,即Flutter自己维护了一个Flutter页面的页面栈。这样,原来我们最熟悉的那套在Native原生上的方案在Flutter上无法直接运作起来。

针对这个问题,可能很多人会想到去注册监听Flutter的NavigatorObserver,这样就知道Flutter页面的进栈(push)和出栈(pop)事件。但是这会有两个问题:

  • 假设A、B两个页面先后进栈(A enter -> A leave -> B enter)。然后B页面返回退出(B leave),此时A页面重新可见,但是此时是收不到A页面push(A enter)的事件。
  • 假设在A页面弹出一个Dialog或者BottomSheet,而这两类也会走push操作,但实际上A页面并未离开。

好在Flutter的页面栈不像Android Native的页面栈那么复杂,所以针对第一个问题,我们可以维护一个和页面栈匹配的索引列表。当收到A页面的push事件时,往队列里塞入A的索引。当收到B页面的push事件时,检测列表内是否有页面,如有,则对列表最后一个页面执行离开页面事件,再对B页面执行进入页面事件,接着往队列里塞B的索引。当收到B页面的pop事件时,先对B页面执行离开页面事件记录,再对队列里存在的最后一个索引对应的页面(假设为A)进行判断是否在栈顶(ModalRoute.of(context).isCurrent),如果是,则对A页面执行进入页面事件。

针对第二个问题,Route类内有个成员变量overlayEntries,可以获取当前Route对应的所有图层OverlayEntry,在OverlayEntry对象中有个成员变量opaque可以判断当前这个图层是否全屏覆盖,从而可以排除Dialog和BottomSheet这种类型。再结合问题1,还需要在上述方案中加上对push进来的新页面来做判断是否为一个有效页面。如果是有效页面,才对索引列表中前一个页面做离开页面事件,且将有效页面加到索引列表中。如果不是有效页面,则不操作索引列表。

以上并不是闲鱼的方案,只是笔者给出的一个建议。因为闲鱼APP在一开始落地Flutter框架时,就没有使用Flutter原生的页面栈管理方案,而是采用了Native+Flutter混合开发的方案,因此接下来也是基于此来阐述闲鱼的方案。

闲鱼的方案如下(以Android为例,iOS同理):

image

image

注:首次打开指的是基于混合栈新打开一个页面,非首次打开指的是通过回退页面的方式,在后台的页面再次到前台可见。

看到这个方案可能会有人问,为什么这么绕,为什么不全部交给Native侧去直接管理呢?交给Native侧去直接管理这样做针对非首次打开这个场景是合适的,但是对首次打开这个场景却是不合适的。但是在首次打开这个场景下,onResume时Flutter页面尚未初始化,此时还不知道页面信息,因此也就不知道进入了什么页面,所以需要在Flutter页面初始化(init)时再回过来调Native侧的进入页面埋点接口。而为了避免开发人员去关注是否为首次打开Flutter页面,因此我们统一在Flutter侧来直接触发进入/离开页面事件。

曝光坑位

先讲下曝光坑位在我们这里的定义,我们认为图片和文本是有曝光意义的,其他用户看不见的是没有曝光意义的,在此之上,当一个坑位同时满足以下两点时才会被认为是一次有效曝光:

  • 坑位在屏幕可见区域中的面积大于等于坑位整体面积的一半。
  • 坑位在屏幕可见区域中停留超过500ms。

基于此定义,我们可以很快得出如下图所示的场景,在一个可以滚动的页面上有A、B、C、D共4个坑位。其中:

  • 坑位A已经滑出了屏幕可见区域,即invisible;
  • 坑位B即将向上从屏幕中可见区域滑出,即visible->invisible;
  • 坑位C还在屏幕中央可视区域内,即visible;
  • 坑位D即将滑入屏幕中可见区域,invisible->visible;

image

那么我们的问题就是如何算出坑位在屏幕内曝光面积的比例。要算出这个值,需要知道以下几个数值:

  • 容器相对屏幕的偏移量
  • 坑位相对容器的偏移量
  • 坑位的位置和宽高
  • 容器的位置和宽高

其中坑位和容器的宽和高很容易获取和计算,这里就不再累述。

获得容器相对屏幕的偏移量

//监听容器滚动,得到容器的偏移量
double _scrollContainerOffset = scrollNotification.metrics.pixels;

获得坑位相对屏幕的偏移量

//曝光坑位Widget的context
final RenderObject childRenderObject = context.findRenderObject();
final RenderAbstractViewport viewport = RenderAbstractViewport.of(childRenderObject);
if (viewport == null) {
  return;
}
if (!childRenderObject.attached) {
  return;
}
//曝光坑位在容器内的偏移量
final RevealedOffset offsetToRevealTop = viewport.getOffsetToReveal(childRenderObject, 0.0);

逻辑判断

if
 (当前坑位是invisible && 曝光比例 >= 0.5) {
  记录当前坑位是visible状态
  记录出现时间
} 
else 
if
 (当前坑位是visible && 曝光比例 < 
0.5
) {
  记录当前坑位是invisible状态  
if
 (当前时间-出现时间 > 500ms) {
    调用曝光埋点接口
  }
}

点击坑位

点击坑位埋点没什么难点,很容易就可以想到下面的方案:

image

效果

经过多轮迭代和优化,目前线上Flutter页面的埋点准确率已经达到100%,有力地支持了业务的分析和判断。同时这套方案让业务同学在做开发时,对于页面进入/离开、曝光坑位可以做到无感知,即不用关心何时去触发,做到了简单易用和无侵入性。

未来

此外,针对页面进入/离开这个场景,由于闲鱼是基于Flutter Boost混合栈的方案,因此我们的解决方案还不够通用。不过未来随着闲鱼上的Flutter页面越来越多,我们后续也会去实现基于Flutter原生的方案。

原文发布时间为:2019-08-12
本文作者:兰昊
本文来自云栖社区合作伙伴“阿里技术”,了解相关信息可以关注“阿里技术”。

相关文章
|
11天前
|
设计模式 移动开发 开发框架
如何学习 Flutter 框架?
学习 Flutter 需要耐心和持续的努力,通过系统的学习、实践、交流和不断跟进最新技术,你将逐渐掌握 Flutter 框架,并能够开发出高质量的移动应用。
|
11天前
|
开发框架 移动开发 Dart
Flutter 框架的缺点
以上缺点并不意味着 Flutter 框架不优秀,只是在使用过程中需要开发者根据具体的项目需求和场景,充分考虑这些因素,并采取相应的措施来克服或缓解这些问题,以充分发挥 Flutter 的优势,开发出高质量的移动应用。
|
11天前
|
IDE 开发工具 Android开发
Flutter 框架的优点
综上所述,Flutter框架以其跨平台一致性、高性能表现、丰富的组件和插件生态、热重载等诸多优点,为移动应用开发带来了全新的体验和强大的开发能力,成为了越来越多开发者的首选框架。
|
21天前
|
缓存 JavaScript API
Flutter&鸿蒙next 状态管理框架对比分析
在 Flutter 开发中,状态管理至关重要,直接影响应用的性能和可维护性。本文对比分析了常见的状态管理框架,包括 setState()、InheritedWidget、Provider、Riverpod、Bloc 和 GetX,详细介绍了它们的优缺点及适用场景,并提供了 Provider 的示例代码。选择合适的状态管理框架需考虑应用复杂度、团队熟悉程度和性能要求。
85 0
|
3月前
|
开发框架 Android开发 iOS开发
Flutter相关痛点解决问题之淘特选择桌面端开发框架如何解决
Flutter相关痛点解决问题之淘特选择桌面端开发框架如何解决
|
5月前
|
开发框架 移动开发 Android开发
构建高效移动应用:探索Flutter开发框架
【6月更文挑战第28天】随着移动设备的普及,用户对移动应用的需求日益增长。开发者面临着在众多平台间提供无缝体验的挑战。本文深入探讨了Flutter框架如何通过其跨平台特性、热重载功能以及丰富的组件库简化移动应用的开发流程,同时确保高性能和优雅的用户界面设计。
73 2
|
5月前
|
移动开发 Dart 前端开发
探秘移动开发新纪元:Flutter框架的跨平台魅力
Flutter,谷歌的开源UI工具包,不仅革新前端开发,其跨平台特性延伸至后端,通过Dart语言统一开发流程,提升效率与一致性。使用Aqueduct框架,开发者可构建REST API,如创建新项目、定义数据模型和控制器,实现前后端同栈开发。Flutter与Dart的结合正重塑移动开发,开启全栈新纪元。
99 2
|
5月前
|
开发框架 开发者 UED
Flutter作为一款跨平台的移动应用开发框架,自然也提供了丰富的工具和功能来支持可访问性和无障碍设计
【6月更文挑战第11天】Flutter是一款注重可访问性设计的跨平台移动应用开发框架,提供语义化组件、文本缩放、对比度调整、动态内容更新通知和键盘导航等功能,支持无障碍体验。开发者应结合简化操作、清晰反馈、多输入方式支持及测试优化等原则,以创建包容性更强的应用,满足不同用户需求,体现社会责任。
64 1
|
5月前
|
开发框架 Rust Dart
Flutter、Electron 和 Tauri 框架简介
Flutter、Electron 和 Tauri 框架简介
181 0
|
6月前
|
数据库 Android开发
Android数据库框架-GreenDao入门,2024年最新flutter 页面跳转动画
Android数据库框架-GreenDao入门,2024年最新flutter 页面跳转动画
Android数据库框架-GreenDao入门,2024年最新flutter 页面跳转动画