Android無(wú)痕埋點(diǎn)(1)— 點(diǎn)擊監(jiān)聽(tīng)

背景

很多時(shí)候,因?yàn)楫a(chǎn)品都會(huì)要獲取用戶的行為,需要客戶端進(jìn)行相關(guān)的埋點(diǎn)。埋點(diǎn)主要分為兩種:

  1. 侵入式埋點(diǎn)
    在每個(gè)需要埋點(diǎn)的地方手動(dòng)添加代碼
  • 優(yōu)點(diǎn):埋點(diǎn)準(zhǔn)確,可以精確描述不同組件之間的關(guān)聯(lián)
  • 缺點(diǎn):代碼耦合度高,后期難以維護(hù),不需要的埋點(diǎn)需要手動(dòng)刪除
  1. 無(wú)痕埋點(diǎn)
    通過(guò)全局監(jiān)聽(tīng)或AOP技術(shù)給所有的View添加埋點(diǎn)
  • 優(yōu)點(diǎn):代碼耦合度低,靈活度高,不同項(xiàng)目可復(fù)用
  • 缺點(diǎn):沒(méi)有侵入式埋點(diǎn)精準(zhǔn),無(wú)法描述兩個(gè)組件之間的關(guān)聯(lián)

面試一句話

無(wú)痕埋點(diǎn)就是一種通過(guò)全局監(jiān)聽(tīng)或者AOP技術(shù)省去手動(dòng)埋點(diǎn)的技術(shù),它和代碼耦合度低,靈活度高,適用于組件間關(guān)聯(lián)性不強(qiáng)的業(yè)務(wù)埋點(diǎn)


Code

完整代碼可見(jiàn) -- 代碼路徑


技術(shù)點(diǎn)

無(wú)痕埋點(diǎn)的技術(shù)路徑包括View操作的攔截,上報(bào)信息的設(shè)置與埋入,上報(bào)

  1. 點(diǎn)擊監(jiān)聽(tīng)
  2. 滑動(dòng)監(jiān)聽(tīng)
  3. 埋點(diǎn)信息
  4. ASpect監(jiān)聽(tīng)

點(diǎn)擊監(jiān)聽(tīng)

實(shí)現(xiàn)方法

通過(guò)給View設(shè)置setAccessibilityDelegate來(lái)獲取sendAccessibilityEvent回調(diào)監(jiān)聽(tīng)View的點(diǎn)擊事件

原理

  1. 當(dāng)View被點(diǎn)擊時(shí)候,最后都會(huì)走到performClick()方法
    public boolean performClick() {
          // We still need to call this method to handle the cases where performClick() was called
          // externally, instead of through performClickInternal()
          notifyAutofillManagerOnClick();
    
          final boolean result;
          final ListenerInfo li = mListenerInfo;
          if (li != null && li.mOnClickListener != null) {
              playSoundEffect(SoundEffectConstants.CLICK);
              li.mOnClickListener.onClick(this);
              result = true;
          } else {
              result = false;
          }
          // 關(guān)鍵方法
          sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
          notifyEnterOrExitForAutoFillIfNeeded(true);
    
          return result;
      }
    
  2. 再看sendAccessibilityEvent()方法,可以看到只要view的mAccessibilityDelegate為非空的,就會(huì)觸發(fā)sendAccessibilityEvent()回調(diào)
    public void sendAccessibilityEvent(int eventType) {
        if (mAccessibilityDelegate != null) {
            mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
        } else {
            sendAccessibilityEventInternal(eventType);
        }
    }
    
  3. 所以我們只需要給View設(shè)置AccessibilityDelegate,就能對(duì)點(diǎn)擊事件進(jìn)行監(jiān)聽(tīng),host代表被監(jiān)聽(tīng)的View,eventType代表事件類(lèi)型,比如點(diǎn)擊、輸入
    view.setAccessibilityDelegate(new View.AccessibilityDelegate(){
        @Override
        public void sendAccessibilityEvent(View host, int eventType) {
            super.sendAccessibilityEvent(host, eventType);
            Log.d(TAG, "sendAccessibilityEvent: ");
        }
    });
    
  4. 對(duì)于層級(jí)很深的ViewGroup,我們可以通過(guò)遍歷的方式,對(duì)他層級(jí)內(nèi)的View和ViewGroup添加監(jiān)聽(tīng),為了避免動(dòng)態(tài)添加的View沒(méi)有添加監(jiān)聽(tīng),我們需要給ViewGroup添加childView的監(jiān)聽(tīng)
    /**
     * 設(shè)置Activity頁(yè)面中View的事件監(jiān)聽(tīng)
     *
     * @param activity
     */
    public void setTracker(Activity activity) {
        // 找到根路徑的View
        View contentView = activity.findViewById(android.R.id.content);
        if (contentView != null) {
            setViewTracker(contentView, null);
        }
    }
    
    /**
     * 設(shè)置Fragment頁(yè)面中View的事件監(jiān)聽(tīng)
     *
     * @param fragment
     */
    public void setTracker(Fragment fragment) {
        View contentView = fragment.getView();
        if (contentView != null) {
            setViewTracker(contentView, fragment);
        }
    }
    
    
    /**
     * 設(shè)置View上的事件監(jiān)聽(tīng)
     *
     * @param view
     */
    public void setTracker(View view) {
        if (view != null) {
            setViewTracker(view, null);
        }
    }
    
    /**
     * 判斷view是否需要埋點(diǎn),目前默認(rèn)只要可以點(diǎn)擊的都是true
     *
     * @param view
     * @return
     */
    private boolean needTracker(View view) {
        return true;
    }
    
    
    /**
     * 對(duì)每個(gè)View添加埋點(diǎn)的監(jiān)聽(tīng)
     *
     * @param view
     * @param fragment
     */
    private void setViewTracker(View view, Fragment fragment) {
        if (needTracker(view)) {
            if (fragment != null) {
                view.setTag(FRAGMENT_TAG_KEY, fragment);
            }
            view.setAccessibilityDelegate(this);
        }
        if (view instanceof ViewGroup) {
            int childCount = ((ViewGroup) view).getChildCount();
            for (int i = 0; i < childCount; i++) {
                setViewTracker(((ViewGroup) view).getChildAt(i), fragment);
            }
            ((ViewGroup) view).setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
                @Override
                public void onChildViewAdded(View parent, View child) {
                    setTracker(parent);
                }
    
                @Override
                public void onChildViewRemoved(View parent, View child) {
                    setTracker(parent);
                }
            });
            if (needTracker(view)) {
                view.setAccessibilityDelegate(this);
                viewScrollListener.setScrollListener(view);
            }
        }
    }
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容