揭祕!如何用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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索