Code前端首页关于Code前端联系我们

设计高精度Flutter用户行为跟踪框架

terry 2年前 (2023-09-23) 阅读数 68 #移动小程序

用户行为跟踪用于记录用户在操作过程中发生的一系列行为。也是商业判断的基础数据库。如果缺失或不准确,就会给企业带来问题。造成无法挽回的损失。在将业务代码从Native迁移到Flutter的过程中,闲鱼发现Native系统原有的隐藏方案无法应用到Flutter系统中。而仅仅将业务功能迁移到线上,对于企业来说是极其不负责任的。因此,经过不断的研究,我们在 Flutter 上开发了极其精准的用户行为跟踪解决方案。

用户行为埋点的放置

这里先说一下我们如何定义用户行为埋点。在下一个用户时间轴中,用户进入页面A后,出现X按钮,然后点击这个按钮,然后打开一个新的页面B。 高准确率的Flutter用户行为埋点框架如何设计

此时间线中发生了以下 5 个埋藏事件:

  • 进入页面 A。 A 面的第一帧已完成并处于对焦状态。
  • 曝光坑X。按钮
  • 单击X坑。用户对X按钮的内容很感兴趣并点击了它。 X 按钮响应一次单击,然后您应该打开一个新页面。
  • 离开A面。 A 面失去焦点。
  • 进入B页。页面 B 的第一帧已渲染并聚焦。

这里,埋点最重要的是时机,即事件中的哪个时间点触发哪个埋点。我们来看看闲鱼在Flutter上的实现方案。

实现方案

进入/退出页面

原生开发中,Android页面监听Activity的onResume和onPause事件作为页面进入和退出事件。同样,iOS页面监听UIViewController的viewWillAppear和viewDidDisappear事件作为页面进入和退出事件。同时,整个站点包由Android和iOS操作系统维护。

在Flutter中,Android和iOS页面使用FlutterActivity和FlutterViewController作为存储Flutter页面的容器。通过这个容器,Flutter页面可以在原生页面内切换,这意味着Flutter维护了Flutter本身。一组页面。这样一来,事实证明我们最了解的Native解决方案并不能直接在Flutter上运行。

针对这个问题,很多人可能会想到注册一个NavigatorObserver来监听Flutter,这样就可以看到Flutter页面的push和pop事件了。然而,这样做有两个问题:

  • 假设页面 A 和 B 依次放入堆栈中(条目 A -> 退出 A -> 条目 B)。然后B方返回并退出(B离开)。然后页面A又可见了,但是没有收到页面A的push(A Enter)事件。
  • 假设A页面上出现了一个对话框或底部选项卡,这两种类型也执行推送操作,但A页面实际上并没有离开。

幸运的是,Flutter 的页面堆栈并不像 Android Native 的页面堆栈那么复杂,所以针对第一个问题,我们可以维护一个与页面堆栈对应的索引列表。当收到页面A的推送事件时,将A的索引插入到队列中。当收到页面B的推送事件时,检查列表中是否有该页面。如果有,则在列表的最后一页上执行页面退出事件,然后在B页上执行页面进入事件,然后将B的索引插入队列中。当收到页面B的pop事件时,首先执行页面B的退出页面事件记录,然后判断队列最后一个索引(假设A)对应的页面是否在栈顶(ModalRoute.of(context).isCurrent ),如果有,则执行页面A的进入页面事件。

对于第二个问题,Route类有一个overlayEntries成员变量,可以获取所有与当前图层匹配的OverlayEntry图层。路线。 OverlayEntry对象有一个opaque成员变量,可以判断当前层是否覆盖整个屏幕,因此可以排除Dialog和BottomSheet类型。结合问题1,上面的方案还应该添加推送的新页面来判断是否是有效页面。如果是有效页面,则在索引列表的前一页上执行页面离开事件,并将有效页面添加到索引列表中。如果它不是有效页面,则索引列表将不起作用。

以上并非闲鱼的设计,只是作者的建议。因为闲鱼APP最初推出Flutter框架时,并没有采用Flutter原生的页面堆栈管理器方案,而是采用Native+Flutter混合开发方案。详情请参阅之前的文章《已开源|码上用它开始Flutter混合开发——FlutterBoost》。所以接下来,就是要以此为基础来解释闲鱼的计划。

闲鱼的解决方案如下(我们以Android为例,iOS同理): 高准确率的Flutter用户行为埋点框架如何设计高准确率的Flutter用户行为埋点框架如何设计

注:首次打开表示基于混合栈打开新页面,非首次打开表示滚动返回页面。后台页面再次在前台可见。

看到这个方案可能有人会问,为什么这么复杂呢?为什么不将其全部留在本机端以进行直接控制?如果直接在原生页面处理,对于非首次打开来说还好,但对于首次打开的场景就不好了。但是这个场景第一次打开的时候,onResume时Flutter页面还没有初始化,目前页面信息未知,所以不知道进入的是哪个页面,所以时候就得回去设置原生页面Flutter页面被初始化(init)。输入侧埋面。为了让开发者不必关注是否是第一次打开Flutter页面,我们直接在Flutter页面上触发进入/退出事件。

曝光坑

首先我们来说一下曝光坑的定义。我们认为图像和文字具有曝光意义,如果对其他用户不可见,则不具有曝光意义。另外,当一个凹坑A同时满足以下两点时,才被认为是有效曝光:

  • 凹坑在屏幕可见区域的面积大于等于一半坑的总面积。
  • 坑在屏幕可见区域停留时间超过500毫秒。

根据这个定义,我们可以快速画出一个如下图所示的场景。一个可滚动的页面上有A、B、C、D 4个坑。其中:

  • A坑已经滑出屏幕可见区域,即不可见;
  • B坑从屏幕可见区域向上滑动,即可见->不可见;
  • C坑仍在屏幕中央可见区域内,即可见;
  • 坑D滑入屏幕可见区域,不可见->可见;

高准确率的Flutter用户行为埋点框架如何设计

那么我们的问题就是如何计算屏幕上坑的暴露面积所占的比例。要计算该值,您需要知道以下值:

  • 容器相对于屏幕的偏移
  • 凹坑相对于容器的偏移
  • 凹坑的位置、宽度和高度
  • 位置而容器宽度和高度之和

坑和槽的宽度和高度可以很容易获得和计算,这里不再赘述。

水箱相对于屏幕的位移

//监听容器滚动,得到容器的偏移量
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) {

    调用曝光埋点接口

  }

}复制代码

点击坑

点击坑埋点不难,容易思考如下方案: 高准确率的Flutter用户行为埋点框架如何设计

效果

经过多次迭代优化,目前在线Flutter页面准确率已达到100%,有效支撑业务分析判断。同时,该方案可以让商科生在开发时无意识地了解网站的进入/退出和暴露坑,意味着他们不必担心何时触发,因此使用方便且无侵入性。

未来

另外,对于页面进入/退出的场景,由于闲鱼是基于Flutter Boost混合栈构建的,所以我们的方案还不够通用。不过,由于未来闲鱼上会出现越来越多的 Flutter 站点,因此我们后续也会推出基于原生 Flutter 的解决方案。

作者:闲鱼科技
链接:https://juejin.im/post/5d011f7c51882559ef78e419
来源:掘金版权归作者所有。商业转载请联系作者获得许可。非商业转载请注明来源。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门