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


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


用户行为埋点定位架构


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


这个时间轴上有以下 5 个埋点事件发生:
  • 进入 A 页面。A 页面首帧渲染完毕,并得到了焦点。
  • 曝光坑位 X 。按钮X处于手机屏幕内,且停留一段时间,让用户可见可触摸。
  • 点击坑位 X 。用户对按钮X的内容很感兴趣,因而点击了它。按钮 X 响应点击,而后须要打开一个新页面。
  • 离开 A 页面。A 页面失去焦点。
  • 进入 B 页面。B 页面首帧渲染完毕,并得到焦点。

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

实现方案app


进入/离开页面

在 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 同理):


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

看到这个方案可能会有人问,为何这么绕,为何不所有交给 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;

 


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

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

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


得到容器相对屏幕的偏移量flex


  
  
  
   
   
            
   
   
  1. 优化

  2. ui

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


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


  
  
  
   
   
            
   
   
  1. url

  2. spa

//曝光坑位Widget的contextfinal 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 页面的埋点准确率已经达到 100% ,有力地支持了业务的分析和判断。同时这套方案让业务同窗在作开发时,对于页面进入/离开、曝光坑位能够作到无感知,即不用关心什么时候去触发,作到了简单易用和无侵入性。

将来


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


推荐阅读


5G时代|闲鱼在Flutter&FaaS云端一体化架构的探索实践之路

如何管理一个大型开源仓库?淘系带你一探究竟

我就知道你“在看”

本文分享自微信公众号 - 淘系技术(AlibabaMTT)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索