RecycleView的有效埋點問題

問題

PM需要獲取當前條目的有效曝光給大數(shù)據(jù)分析推廣適用,因此需要獲取recycleView的有效曝光的埋點數(shù)據(jù)眶痰;

  • 要求
    1. RecycleView中復(fù)用條目不用重復(fù)埋點,除非下拉刷新數(shù)據(jù)存哲;
    2. 待確定:條目UI顯示超過50%方可埋點七婴,否則不埋點;

分析

由于RecycleView的四級緩存機制打厘,當我們在onBinding中綁定數(shù)據(jù)時埋點會增加二級緩存的埋點,導(dǎo)致獲取有效曝光不準確問題?如何解決該問題:兩種方式

View繪制流程
  • 平臺測目前在用重寫onAttachedToWindow()和onDetachedFromWindow()這兩個方法在RecyclerView內(nèi)部會在View移動出可視區(qū)域的時候被觸發(fā);
    1. 當 Adapter 創(chuàng)建的 View 在被滑動進屏幕的時onViewAttachedToWindow() 會直接回調(diào)嵌施,反之莽鸭,在列表項 View 被窗口分離(即滑動離開了當前窗口界面的)的時onViewDetachedToWindow() 會立馬被調(diào)用。
  1. 根據(jù)以上特性足淆,在adapter中重寫onViewAttachedToWindow(RecycleView.ViewHolder)可以獲取當前列表剛剛滑進屏幕的條目布局信息礁阁,那么埋點的數(shù)據(jù)如何綁定?
    • 重寫viewHolder通過tag保存和讀取姥闭,平臺已經(jīng)封裝ViewHolder,需要修改每個Delegate中的viewHolder繼承該類
    public class ViewHolder extends androidx.recyclerview.widget.RecyclerView.ViewHolder {
     private SparseArray<View> mViews = new SparseArray();
     private SparseArray<Object> mKeyedTags;
    
     public ViewHolder(View itemView) {
         super(itemView);
     }
    
     public void setTag(int key, Object tag) {
         if (key >>> 24 < 2) {
             throw new IllegalArgumentException("The key must be an application-specific resource id.");
         } else {
             if (this.mKeyedTags == null) {
                 this.mKeyedTags = new SparseArray(2);
             }
    
             this.mKeyedTags.put(key, tag);
         }
     }
    
     public Object getTag(int key) {
         return this.mKeyedTags != null ? this.mKeyedTags.get(key) : null;
     }
    
    1. adapter通過Delegate添加每個條目布局和數(shù)據(jù)棚品,然后在Delegate的 onBindViewHolder中設(shè)置tag屬性弥姻,并將埋點所需的條目數(shù)據(jù)添加進去
public class PtClientAdapter extends JobPtAbsDelegationAdapter {
    public PtClientAdapter(Activity activity, List<PtCateListBean.PtBaseListBean> items, OnOptCallBack onOptCallBack,
                           OnItemClickCallback onItemClickCallback,ActionUniteInterface callBack) {

        this.delegatesManager.addDelegate(new PtClientNormalDelegate(activity , mCallBack)); //普通兼職職位
        this.delegatesManager.addDelegate(new PtListBannersDelegate(activity , mCallBack));//輪播圖
        this.delegatesManager.addDelegate(new PtOnlineTaskDelegate(activity, onItemClickCallback , mCallBack));//線上任務(wù)
        this.delegatesManager.addDelegate(new PtHotCateDelegate(activity, onFilterCallback , mCallBack)); //你可能在找
        this.delegatesManager.addDelegate(new PtEncourageVideoDelegate(activity , mCallBack));//激勵視頻
        this.delegatesManager.addDelegate(new PtResumeDelegate(activity , mCallBack)); //簡歷引導(dǎo)
        this.delegatesManager.addDelegate(new PtCustomDelegate(activity , mCallBack)); //會員定制
        this.delegatesManager.addDelegate(new PtOperatingItemDelegate(activity , mCallBack)); //猜你喜歡
    }
}

//在Delegate中設(shè)置tag
public class PtClientNormalDelegate extends AdapterDelegate{
    @Override
    protected void onBindViewHolder(@NonNull List<PtCateListBean.PtBaseListBean> items, final int position, @NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) {
          
        final PtCateListBean.PositionNormal positionNormalBean = (PtCateListBean.PositionNormal) items.get(position);
        final NormalViewHolder viewHolder = (NormalViewHolder) holder;
        //設(shè)置tag,并把當前條目信息加入緩存
         viewHolder.setTag(R.id.id_tag_detail_bean, positionNormalBean);
    }
}
  1. 由于每個Delegate對應(yīng)的javaBean對象類都是不同,直接寫到adapter中會導(dǎo)致無法很輕松理解薪缆,平臺已經(jīng)封裝過了,在Adapter中通過DeleGateManager將onViewAttachedToWindow()分發(fā)給每一個Delegate類拣帽,因此可以直接重寫Delegate的onViewAttachedToWindow(ViewHolder holder)
 @Override
 protected void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
     super.onViewAttachedToWindow(holder);
     ViewHolder viewHolder = (ViewHolder) holder;

     Object tag = viewHolder.getTag(R.id.id_tag_detail_bean);

     if (tag instanceof  PtCateListBean.PositionNormal) {
         PtCateListBean.PositionNormal positionNormalBean = (PtCateListBean.PositionNormal) tag;
         int adapterPosition = viewHolder.getAdapterPosition();
         Log.e("shiq" , "當前被顯示了 - onViewAttachedToWindow : " + positionNormalBean.title  + "   " + positionNormalBean + " ---- 列表中的位置為: " + adapterPosition);
     }
 }
  • 以上對于每個Delegate都在自己類中添加有效埋點數(shù)據(jù)减拭。便于后期維護,但有一個問題拧粪,PM要求相同的埋點滑動時只埋一次沧侥。onViewAttachedToWindow會每次顯示均會調(diào)用一次。如何解決呢宴杀?
    1. 在javaBean中設(shè)置boolean值記錄當前是否首次顯示被埋點過了,如果埋點標記為true旷余,后續(xù)顯示均不會埋點了:優(yōu)點簡單扁达,缺點如果javaBean是三方的不易修改;
    2. 在adapter中創(chuàng)建集合記錄已經(jīng)被標記埋點過的javaBean數(shù)據(jù),如何區(qū)分javaBean唯一性罩驻,可以通過hashCode + position標記,如果首次顯示埋點后添加記錄砾跃,再次顯示后過濾掉即可: 優(yōu)點:不修改原有數(shù)據(jù)节吮,缺點:每次都需判斷是否在集合中,性能有所影響透绩;
  • 不足之處: onViewAttachedToWindow無法區(qū)分當前UI是否被顯示超過50%壁熄;

通過RecycleView的滑動監(jiān)聽

  • 通過監(jiān)聽RecycleView的滑動事件草丧,獲取當前屏幕顯示的條目信息,根據(jù)條件刪選即可昌执!
  1. 重寫RecycleView的onScrollStateChanged诈泼,onScrolled方法
 @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        switch (newState) {
            case RecyclerView.SCROLL_STATE_IDLE:
//            case RecyclerView.SCROLL_STATE_DRAGGING:
//            case RecyclerView.SCROLL_STATE_SETTLING:
                findScreenVisibleViewsAndNotify();
                break;
        }
    }

    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (dx == 0 && dy == 0) { //如果當前是首次進入時設(shè)置
            findScreenVisibleViewsAndNotify();
        }
    }
  1. RecycleVeiw的LinearLayoutManager布局獲取當前屏幕顯示的首位和末位的條目,不足: 結(jié)果并不準確岖赋,findLastVisibleItemPosition大于當前顯示位置瓮孙;
 range[0] = manager.findFirstVisibleItemPosition();
 range[1] = manager.findLastVisibleItemPosition();
  1. 對于上述中的不足之處,我們應(yīng)該如何優(yōu)化使之適合我們的要求衷畦,這里用到了view.getGlobalVisibleRect()獲取的是view可見區(qū)域相對與屏幕來說的坐標位置;


    image
 Rect rect = new Rect();
 boolean cover = view.getGlobalVisibleRect(rect);

  //item邏輯上可見:可見且可見高度(寬度)>view高度(寬度)50%才行
  boolean visibleHeightEnough = orientation == OrientationHelper.VERTICAL && rect.height() > view.getMeasuredHeight() / 2;
 boolean visibleWidthEnough = orientation == OrientationHelper.HORIZONTAL && rect.width() > view.getMeasuredWidth() / 2;
 boolean isItemViewVisibleInLogic = visibleHeightEnough || visibleWidthEnough;
 if (cover  && isItemViewVisibleInLogic) {
    //去重斤程,可埋點的數(shù)據(jù)
}
  1. 我們已經(jīng)獲取到了當前坐標position是否被顯示且滿足條件忿墅,對于去重,依然采用View繪制中兩種方式疚脐,這里使用第二種邢疙,通過集合保存已被埋點數(shù)據(jù),定義統(tǒng)一接口給adapter適配用于數(shù)據(jù)獲扰庇巍;
//數(shù)據(jù)區(qū)分接口
public interface IRecyclerViewAdapter {
    /**
     * 根據(jù)position獲取item的數(shù)據(jù)
     */
    Object getCurrentItemData(int position);

    int getCurrentSize();

}
// 獲取到需展示數(shù)據(jù)接口
public interface OnRecycleExposureListener {

    /**
     * 當前被展示的數(shù)據(jù)集合
     * @param exposureBeans
     */
    void onExposure(List<ExposureBean> exposureBeans);
}

Object itemData = null;
if (cover  && isItemViewVisibleInLogic) {
    RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
    if (adapter != null && adapter instanceof IRecyclerViewAdapter){
         int currentSize = ((IRecyclerViewAdapter) adapter).getCurrentSize();
         if (currentSize > position){
             itemData = ((IRecyclerViewAdapter) adapter).getCurrentItemData(position);
          }
    }
}

if (itemData == null)  return;//如果不存在數(shù)據(jù)蛮原,跳過本次循環(huán)

if (mManager.addResource(mRule.createItemID(itemData, position))) {
      mAllShowList.add(new ExposureBean(itemData, view, position));
 }
  • 提供通用的去重規(guī)則接口另绩,便于后續(xù)擴展花嘶,這里使用規(guī)則為javaBean的hanshCode + position
  • 數(shù)據(jù)管理集合蹦漠,由于每次均需要查詢是否在其中,這里為了效率mManager推薦使用hashSet拆撼,盡量避免使用ArrayList,當列表數(shù)據(jù)過大時會影響效率!
  • 獲取到的數(shù)據(jù)保存在mAllShowList集合中,通過接口回掉或者動態(tài)代理(如果不太清楚adapter類型竭贩,在view層通過 instanceof OnRecycleExposureListener)
  1. 在adapter中實現(xiàn)接口,分發(fā)給每個Delegate去埋點,也可以通過view.setTag和getTag使用獲攘袅俊;
public void onExposure(List<ExposureBean> exposureBeans) {

   if (exposureBeans != null && !exposureBeans.isEmpty()) {
        for (ExposureBean bean : exposureBeans) {
          //根據(jù)當前位置獲取設(shè)置AdapterDelegate
           int itemViewType = this.delegatesManager.getItemViewType(items, bean.position);
           AdapterDelegate delegateForViewType = this.delegatesManager.getDelegateForViewType(itemViewType);
           if (bean.itemData != null && delegateForViewType != null)
             delegateForViewType.exPostActionItem(bean.itemData, bean.position);
        }
    }
}
  • 總結(jié): RecycleView的adapter實現(xiàn)IRecyclerViewAdapter提供去重數(shù)據(jù)忆绰,OnRecycleExposureListener返回需要埋點集合可岂,每個Delegate重寫exPostActionItem方法去添加有效曝光的埋點即可!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稚茅,一起剝皮案震驚了整個濱河市平斩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绘面,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晚凿,死亡現(xiàn)場離奇詭異,居然都是意外死亡晃虫,警方通過查閱死者的電腦和手機扣墩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門扛吞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荆责,“玉大人,你說我怎么就攤上這事盲泛。” “怎么了寺滚?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵屈雄,是天一觀的道長。 經(jīng)常有香客問我酒奶,道長,這世上最難降的妖魔是什么惋嚎? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任另伍,我火速辦了婚禮,結(jié)果婚禮上质况,老公的妹妹穿的比我還像新娘。我一直安慰自己结榄,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布邻寿。 她就那樣靜靜地躺著,像睡著了一般绣否。 火紅的嫁衣襯著肌膚如雪挡毅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音取逾,去河邊找鬼苹支。 笑死,一個胖子當著我的面吹牛债蜜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寻定,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晶丘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤捷枯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后淮捆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡攀痊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年苟径,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棘街。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡承边,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出博助,到底是詐尸還是另有隱情,我是刑警寧澤富岳,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布拯腮,位于F島的核電站渤闷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏飒箭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一肩碟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧削祈,春花似錦、人聲如沸髓抑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽网杆。三九已至,卻和暖如春碳却,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昼浦。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留迷帜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓戏锹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锦针。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容