背景

用户行为埋点是用来记录用户在操作时的一系列行为,也是业务做判断的核心数据依据,如果缺失或者不准确将会给业务带来不可恢复的损失。闲鱼将业务代码从Native迁移到Flutter上过程中,发现原先Native体系上的埋点方案无法应用在Flutter体系之上。而如果我们只把业务功能迁移过来就上线,对业务是极其不负责任的。因此,经过不断探索,我们沉淀了一套Flutter上的高准确率的用户行为埋点方案。

用户行为埋点定义

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

这个时间轴上有如下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的页面内(FlutterActivity/FlutterViewController)来进行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混合开发的方案。具体可参考前面的一篇文章《已开源|码上用它开始Flutter混合开发——FlutterBoost》。因此接下来也是基于此来阐述闲鱼的方案。

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

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

看似我们将何时去触发进入/离开页面事件的判断交给Flutter侧,实际上依然跟Native侧的页面栈管理保持了一致,将原先在Native侧做打埋点的时机告知Flutter侧,然后Flutter侧再立刻通过channel来调用Native侧的打埋点方法。那么可能会有人问,为什么这么绕,不全部交给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;

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

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

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

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

//监听容器滚动,得到容器的偏移量
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页面的埋点准确率已经达到100%,有力地支持了业务的分析和判断。同时这套方案让业务同学在做开发时,对于页面进入/离开、曝光坑位可以做到无感知,即不用关心何时去触发,做到了简单易用和无侵入性。

展望

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

原文链接
本文为云栖社区原创内容,未经允许不得转载。

揭秘!一个高准确率的Flutter埋点框架如何设计相关推荐

  1. 分享一个高质量的 小程序UI框架

    先上ui 地址 github.com/weilanwl/Co- 先看这里,我感觉这个ui开源作者肯定是一位ui设计师,介绍就已经很高颜值了,下面我们来观看一下这个ui框架,这个ui框架和其他ui框架不 ...

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

    阿里妹导读:用户行为埋点是用来记录用户在操作时的一系列行为,也是业务做判断的核心数据依据,如果缺失或者不准确将会给业务带来不可恢复的损失.闲鱼将业务代码从Native迁移到Flutter上过程中,发现 ...

  3. 如何设计一个高可用的运营系统

    转载自   如何设计一个高可用的运营系统 这是一篇来自粉丝的投稿,作者[林湾村龙猫]近一年在做关于运营活动方面的设计.本文是他的关于运营活动的总结,Hollis做了一点点修改. 概述 一个产品业务的发 ...

  4. java实现OCR图文识别Tess4j,高准确率高效率

    java实现OCR图文识别Tess4j,高准确率高效率,用最新的词库2秒就可以识别,没有最新词库的找我 傻瓜式调用中文词库,及其方便. 虽然不能达到99%的准确率,但是也能达到90%左右的准确率,而且 ...

  5. Flutter 3 发布了(文末推荐一个免费的在线Flutter学习教程)

    翻译自 Tim Sneath[1] 2022年5月12日的文章 <Introducing Flutter 3>[2] 作者 :Tim Sneath 翻译 :沙漠尽头的狼(谷歌翻译加持) 链 ...

  6. 《猎场》有硬伤!独家揭秘硅谷高管的招募日常

    硅谷Live / 实地探访 / 热点探秘 / 深度探讨 硅谷高管猎头的一天,独家揭秘硅谷高管的招募日常! 前段时间国内正热播着<猎场>电视剧,小探超喜欢!但是问了好基友--硅谷高管小猎头小 ...

  7. 揭秘数极客Android无埋点数据采集原理

    采集数据柯林斯基本分为代码埋点状语从句:无埋点.近年来无埋点的数据采集方案越来越普及,而无埋点的实现方案也有多种,我们今天讨论的问题是数据采集的一种方案,是无需开发人员重复进行采集事件的代码埋点就能达 ...

  8. 高准确率的Edage浏览器翻译插件

    使用Windows10的小伙伴都知道,Windows10自带了一款浏览器.这款浏览器主要的优点就是简洁轻便,不像其它的浏览器那么臃肿,对于普通的浏览网页来说,是非常不错的选择. 安利一款简洁轻便,高准 ...

  9. JAVA用最简单的方法来构建一个高可用的服务端,提升系统可用性

    一.什么是提升系统的高可用性 JAVA服务端,顾名思义就是23体验网为用户提供服务的.停工时间,就是不能向用户提供服务的时间.高可用,就是系统具有高度可用性,尽量减少停工时间.如何用最简单的方法来搭建 ...

最新文章

  1. 【高并发】高并发场景下如何优化加锁方式?看完这篇我确实明白了!!
  2. 在线作图|如何画韦恩图(包含upset图)
  3. STE:入侵植物可选择性富集土壤微生物
  4. Cacti/Nagios监控系统应用场景
  5. windows service自动启动相关设置
  6. POJ 3164 Command Network
  7. Nodejs中获取get请求传过来的参数_note
  8. win10怎么设置默认输入法_电脑输入法怎么设置成搜狗双拼?
  9. WCF Error: 客户端配置部分中,找不到引用协定{0}的默认终结点元素……
  10. 语言模型总结(待完善)
  11. i8一点通如何加载虚拟光驱玩游戏
  12. 关于Render在不同情况的用法
  13. 安装图解:Linux Mint 4.0(Daryna)(或者说完美的桌面系统)
  14. C语言写一个猜数字游戏?我只想玩王者荣耀
  15. Drupal漏洞复现:CVE-2019-6341
  16. Linux下thread编程
  17. Windows 10打开远程桌面的方法
  18. 用PS修改图片上的数字
  19. JAVA JDBC连接步骤代码,SQL注入,处理异常try catch 的快捷键
  20. “0x????????”指令引用的“0x????????”内存。该内存不能为“read或written解决方法

热门文章

  1. php excel 垂直居中,完美实现文字图片水平垂直居中
  2. python绘制混淆矩阵_如何实现python绘制混淆矩阵?
  3. mysql 异步 同步 不支持_MySQL C#异步方法不起作用?
  4. python中用于标识字符串的定界符_001.python-基础-Template的字符串格式化
  5. linux无filelength函数,Linux Shell 自定义函数(定义、返回值、变量作用域)介绍
  6. python查询模块路径_Visual Studio 2017中的Python无法通过“搜索路径”查找模块
  7. python类的特殊方法汇总_Python笔记001-类的特殊方法
  8. 突发,这个国家进入紧急状态!数十年来最严重经济危机、每天停电13小时!外交部发出提醒...
  9. 留守女孩携笔从戎,被录取为空军飞行员
  10. 网红证明题:圆周率其实应该是4!看完真的不明觉厉?