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

2019 年 12 月 20 日

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

实现方案


进入/离开页面


在 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;



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


  • 容器相对屏幕的偏移量

  • 坑位相对容器的偏移量

  • 坑位的位置和宽高

  • 容器的位置和宽高


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


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


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


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


//曝光坑位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 原生的方案。


本文转载自淘系技术公众号。


原文链接:https://mp.weixin.qq.com/s/3LZWyA-rcE6CwTC3hLxoPg


2019 年 12 月 20 日 14:21143

评论

发布
暂无评论
发现更多内容

架构师训练营第二周作业

陈靓-哲露

架构师第二周学习总结

陈靓-哲露

程序员的晚餐 | 6 月 20 日 随便牛肉和翡翠白玉

清远

美食

LeetCode | 5. Longest Common Prefix 最长公共前缀

Puran

Python C# 算法 LeetCode

设计模式作业

qihuajun

ARTS|Week 4 Product, Leadership, and SOLID

Puran

设计模式 LeetCode ARTS活动 Leadership

week3:组合设计模式和单例

Geek_36d3e5

架构师训练营第三周作业

张明森

极客大学架构师训练营

了解 Java 架构

陈皮

LeetCode 2. Add Two Numbers

liu_liu

数据结构 算法 链表 LeetCode

架构师训练营第三周总结

hiqian

跨平台数据库管理神器DataGrip,用上就爱不释手

飞哥

数据库 JetBrains datagrip

孩子教育

王进行

教育 孩子

大话设计模式 | 3. SOLID原则

Puran

设计模式

LeetCode | 4. Palindrome Number 回文数

Puran

Python C# 算法 LeetCode

刘华:想入门软件系统架构设计,看这篇就够了

刘华Kenneth

架构 架构师 故障 容灾 灾备

架构师训练营总结-20200621

caibird1984

极客大学架构师训练营

通证经济=区块链技术+商业模式

CECBC区块链专委会

商业模式 区块链技术 Token 通证经济

week3-学习心得

Geek_36d3e5

Week3 作业

Shawn

架构师训练营 - 第 2 周命题作业

红了哟

本地缓存高性能之王Caffeine

java金融

Java Guava Cache Caffeine 本地缓存 谷歌本地缓存

迎接一次重大的人生升级,让优秀的你,成为大学顶尖生。

叶小鍵

LeetCode 300. Longest Increasing Subsequence

liu_liu

LeetCode

了解 Java 内存模型

陈皮

JMM

效率思维模式与Zombie Scrum

Worktile

敏捷开发

区块链助力新基建

CECBC区块链专委会

区块链技术 联盟链 公链 底层技术

云计算产品的竞争力

韩超

云计算 k8s 公有云 私有云

ARTS Week4

时之虫

ARTS 打卡计划

单例模式与组合模式总结与练习

单例模式 极客大学架构师训练营 组合模式 第三章作业

ArrayList的删除姿势你都知道了吗

java金融

Java 后端 ArrayList 循环删除 ModificationException

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