当前位置: 首页 > 科技观察

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

时间:2023-03-20 18:24:04 科技观察

用户行为埋点用于记录用户在操作过程中的一系列行为,也是业务判断的核心数据依据。如果遗漏或不准确,将给企业带来不可挽回的损失。在将业务代码从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页面中切换Flutter页面,即Flutter维护一个Flutter页面。堆。这样一来,原来我们最熟悉的原生方案就无法直接在Flutter上运行了。针对这个问题,很多人可能会想到注册监听Flutter的NavigatorObserver,这样就会知道Flutter页面的推送(push)和弹出(pop)事件。但是会出现两个问题:假设页面A和B依次入栈(A进入->A离开->B进入)。然后页面B返回退出(B离开),此时页面A再次可见,但是此时无法接收到页面A推送(A进入)的事件。假设A页面弹出一个Dialog或者BottomSheet,这两个类型也会进行push操作,但实际上A页面并没有离开。好在Flutter的页面栈没有AndroidNative那么复杂,所以对于第一个问题,我们可以维护一个与页面栈匹配的索引列表。当接收到页面A的推送事件时,将A的索引插入到队列中。当接收到页面B的push事件时,检查列表中是否有页面,如果有则对列表中的最后一个页面执行离开页面事件,然后对页面B执行进入页面事件,然后插入B进入队列的索引。当接收到页面B的pop事件时,首先对页面B执行页面离开事件记录,然后判断队列中最后一个索引(假设为A)对应的页面是否在栈顶(ModalRoute.of(context).isCurrent),如果是,则执行页面A的页面入口事件。对于第二个问题,Route类中有一个成员变量overlayEntries,可以获取当前Route对应的所有层OverlayEntry,而OverlayEntry对象中的一个成员变量opaque可以判断当前图层是否全屏覆盖,所以该类型的Dialog和BottomSheet。结合问题1,还需要在上述方案中加入一个新推入的页面,判断是否为有效页面。如果是有效页面,则对索引列表中的上一页执行页面离开事件,并将有效页面添加到索引列表中。如果它不是有效页面,则不操作索引列表。以上并非闲鱼的方案,而是作者给出的建议。因为闲鱼APP在最初实现Flutter框架的时候并没有使用Flutter原生的页面栈管理方案,而是采用了Native+Flutter混合开发的方案,所以下面也以此来讲解闲鱼方案。闲鱼的解决方法如下(以安卓为例,iOS同理):注:首次打开是指基于混合栈打开一个新页面。前台可见。看到这个方案,可能有人会问,为什么这么迂回,为什么不全部交给Native端直接管理呢?适用于不是第一次打开的场景,但是适合第一次打开的场景。但这是不合适的。但是这个场景第一次打开的时候,onResume的时候Flutter页面还没有初始化,此时不知道页面信息,所以不知道进入了什么页面,所以需要回过头来在Flutter页面初始化(init)时调用Native端。进入页面埋点界面。为了避免开发者关注是否是第一次打开Flutter页面,我们统一Flutter端直接触发进入/退出页面事件。曝光坑这里先说一下曝光坑的定义。我们认为图文具有曝光意义,其他用户看不到的不具有曝光意义。除此之外,当一个坑点同时满足以下两个条件时:当坑点在屏幕可见区域的面积大于或等于一半时,将被视为有效曝光坑的总面积。坑在屏幕可见区域停留时间超过500ms。根据这个定义,我们可以快速画出下图所示的场景,其中在一个可滚动的页面上有A、B、C、D4个坑。其中:A坑已经滑出屏幕可见区域,即不可见;B坑即将滑出屏幕可见区域,即visible->invisible;C坑还在屏幕中央可见区域,即可见;D坑即将滑入屏幕可见区域,不可见->可见;那么我们的问题就是如何计算坑的暴露面积在画面中的比例。计算这个值需要知道以下几个值:容器相对于屏幕的偏移量坑相对于容器的偏移量坑的位置和宽高容器的位置和宽高其中它们,坑和容器的宽度和高度很容易获取和计算,这里不再赘述。获取容器相对于屏幕的偏移量//监听容器的滚动获取容器的偏移量double_scrollContainerOffset=scrollNotification.metrics.pixels;获取坑位相对于屏幕的偏移//曝光坑Widget的ContextfinalRenderObjectchildRenderObject=context.findRenderObject();finalRenderAbstractViewportviewport=RenderAbstractViewport.of(childRenderObject);if(viewport==null){return;}if(!childRenderObject.attached){return;}//曝光坑在容器中的偏移量finalRevealedOffsetoffsetToRevealTop=viewport.getOffsetToReveal(childRenderObject,0.0逻辑判断if(当前坑位不可见&&曝光率>={0.5)记录当前坑位可见并记录出现时间}elseif(当前坑位可见&&曝光率<0.5){记录当前坑位不可见状态if(当前时间-出现时间>{500ms)调用曝光埋点接口}}点击在坑位点击坑位埋点,Flutter页面埋点准确率达到100%,有力支持业务分析判断换货。同时该方案让商科学生在开发时对页面进入/退出和暴露的坑不敏感,也就是不用担心什么时候触发,简单易用,无侵入性.另外对于以后进入/退出页面的场景,由于闲鱼是基于FlutterBoost混合栈的方案,我们的方案还不够通用。不过以后随着闲鱼上的Flutter页面越来越多,我们以后也会实现基于Flutter的原生方案。