網(wǎng)易HubbleData之Android無埋點(diǎn)實(shí)踐

版權(quán)歸屬于微信公眾號(hào)文章網(wǎng)易HubbleData之Android無埋點(diǎn)實(shí)踐
文末有彩蛋哦?

1 背景

網(wǎng)易HubbleData是一個(gè)洞察用戶行為的數(shù)據(jù)分析系統(tǒng)变汪,提供一套完整的數(shù)據(jù)解決方案殿较。一個(gè)典型的數(shù)據(jù)平臺(tái),對(duì)于數(shù)據(jù)的處理,是由如下的5個(gè)步驟組成的:

其中萌壳,第一個(gè)步驟,也即數(shù)據(jù)采集是最核心的問題倔约。網(wǎng)易HubbleData支持全端數(shù)據(jù)采集佳遣,包括iOS、Android免绿、JS唧席、JAVA等多個(gè)平臺(tái)。本文主要討論Android平臺(tái)的數(shù)據(jù)采集方案嘲驾。業(yè)內(nèi)各家公司從不同角度淌哟,提出了多種技術(shù)方案,這些方案大體上可以歸為三類:

(1) 代碼埋點(diǎn):在某個(gè)事件發(fā)生時(shí)調(diào)用SDK里面相應(yīng)的接口發(fā)送埋點(diǎn)數(shù)據(jù)辽故,百度統(tǒng)計(jì)徒仓、友盟、TalkingData誊垢、Sensors Analytics等第三方數(shù)據(jù)統(tǒng)計(jì)服務(wù)商大都采用這種方案掉弛。

  • 優(yōu)點(diǎn):使用者控制精準(zhǔn)症见,自由地選擇什么時(shí)候發(fā)送數(shù)據(jù);使用者控制精準(zhǔn)殃饿,自由地選擇什么時(shí)候發(fā)送數(shù)據(jù)谋作。
  • 缺點(diǎn):開發(fā)及測(cè)試代價(jià)大;需要等待APP更新乎芳。

(2) 可視化埋點(diǎn):通過可視化工具配置采集節(jié)點(diǎn)遵蚜,在Android端自動(dòng)解析配置并上報(bào)埋點(diǎn)數(shù)據(jù),從而實(shí)現(xiàn)所謂的自動(dòng)埋點(diǎn)奈惑,代表方案是已經(jīng)開源的Mixpanel吭净。

  • 優(yōu)點(diǎn):解放開發(fā)人員,解決了代碼埋點(diǎn)代價(jià)大的問題携取;通過服務(wù)端配置埋點(diǎn)攒钳,解決等待APP更新的問題。
  • 缺點(diǎn):覆蓋功能有限雷滋,只能配置一些公共屬性不撑;埋點(diǎn)只能從當(dāng)前時(shí)刻開始,無法“回溯”晤斩。

(3) 無埋點(diǎn):它并不是真正的不需要埋點(diǎn)焕檬,而是Android端自動(dòng)采集全部事件并上報(bào)埋點(diǎn)數(shù)據(jù),在后端數(shù)據(jù)計(jì)算時(shí)過濾出有用數(shù)據(jù)澳泵,代表方案是國內(nèi)的GrowingIO实愚。

  • 優(yōu)點(diǎn):解放開發(fā)人員,解決了代碼埋點(diǎn)代價(jià)大的問題兔辅;解決了等待APP更新和數(shù)據(jù)“回溯”的問題腊敲;可以自動(dòng)獲取很多啟發(fā)性的信息。
  • 缺點(diǎn):覆蓋的功能有限维苔,不能靈活地自定義屬性碰辅;給網(wǎng)絡(luò)傳輸和耗電等性能帶來更大的負(fù)載。

網(wǎng)易HubbleData的Android SDK早已有之介时,公司內(nèi)部諸如考拉没宾、易信、LOFTER沸柔、美學(xué)循衰、漫畫等多款產(chǎn)品都已接入使用。原有Android SDK采用手動(dòng)代碼埋點(diǎn)的方案褐澎,主要關(guān)注的是事件模型会钝、埋點(diǎn)接口、上報(bào)策略等問題工三。整體架構(gòu)如下圖所示:

代碼埋點(diǎn)雖然使用起來靈活顽素,但是開發(fā)成本較高咽弦,并且一旦上線就很難修改。參考業(yè)界先進(jìn)方案并結(jié)合網(wǎng)易公司內(nèi)部產(chǎn)品的埋點(diǎn)需求胁出,網(wǎng)易HubbleData的Android SDK在代碼埋點(diǎn)整體架構(gòu)的基礎(chǔ)上新增了無埋點(diǎn)功能,本文主要針對(duì)網(wǎng)易HubbleData在Android SDK中無埋點(diǎn)實(shí)踐進(jìn)行簡單分享段审。

2 無埋點(diǎn)關(guān)鍵技術(shù)

2.1 View的唯一ID

2.1.1 如何唯一地標(biāo)識(shí)一個(gè)View全蝶?

SDK內(nèi)部在自動(dòng)收集控件數(shù)據(jù)時(shí),需要將界面上的任何一個(gè)View與其他View區(qū)分開來寺枉。這就需要為界面上的每一個(gè)控件分配一個(gè)唯一的ViewID抑淫。此ViewID除了具有區(qū)分性,還需要具有一致性姥闪,即同一個(gè)View無論界面布局如何動(dòng)態(tài)變化始苇,或者說多次進(jìn)入同一頁面,此ViewID理論上保持不變筐喳。

View中可以找到的特征信息:

  • Id: 靜態(tài)整數(shù)催式。在編譯期,aapt會(huì)生成R類避归,其中包含所有資源ID荣月。

  • Resource Id:開發(fā)者操作控件的唯一標(biāo)識(shí)。一般由開發(fā)者在布局文件中指定android:id梳毙,通過findViewById找到View哺窄。

  • Class Name:View所屬的Class,例如TextView账锹、LinearLayout萌业、ListView、ViewPager等奸柬。

這些特征信息中的Id如果能夠使用生年,是可以直接用作ViewID的,但是鸟缕,從aapt生成id的原則來看晶框,不同版本相同的resource Id對(duì)應(yīng)的整數(shù)Id 是有可能不一樣的,所以沒有辦法使用Id來唯一標(biāo)識(shí)懂从。

Resource Id是開發(fā)者定義的View標(biāo)識(shí)授段,對(duì)于有Resource Id 的View可以說具備了唯一標(biāo)識(shí),那么沒有Resource Id的View番甩,我們考慮通過一個(gè)index屬性來區(qū)分侵贵,index屬性可以取每個(gè)控件所屬父組件的index(也即每個(gè)控件是其父控件的第幾個(gè)孩子),并逐級(jí)向上遍歷找到根節(jié)點(diǎn)缘薛,最后形成一個(gè)View Path即可用來唯一地標(biāo)識(shí)這個(gè)View窍育。

2.1.2 ViewID構(gòu)造

通過上述分析卡睦,我們得到一條View Path:獲取每個(gè)控件自身的ID、類名漱抓、Resource Id以及位于所屬父組件的Index等特征信息表锻,并逐級(jí)向上遍歷找到根節(jié)點(diǎn)。

并結(jié)合該View所在的頁面信息乞娄,我們得到ViewID的構(gòu)造形式如下:

sha-256(page : path)
  • page: ActivityName
  • path: view在控件樹中的全路徑瞬逊,按照如下形式進(jìn)行拼接,其中index為當(dāng)前view所屬父組件的index仪或,id為編寫布局文件時(shí)的android:id屬性值确镊,有則拼接,且index固定為0范删,無則不拼接蕾域。
parent1[index]#id/parent2[index]#id/.../view[index]#id

簡單示例如下:

2.1.3 ViewID優(yōu)化

考慮到在實(shí)際布局中有可能存在一些動(dòng)態(tài)插入、刪除的控件到旦,或者說控件被復(fù)用旨巷,都可能引起View Path的變化,從而導(dǎo)致ViewID不唯一厢绝。為了保證ViewID的一致性契沫,我們從以下幾個(gè)方面著手,對(duì)ViewID進(jìn)行了一定程度地優(yōu)化昔汉。

(1) Index

如上圖所示懈万,當(dāng)頁面布局發(fā)生動(dòng)態(tài)變化時(shí),比如說刪除一個(gè)子view靶病,其他子view所屬父組件的index也可能會(huì)改變会通,為此,我們對(duì)view所屬父組件的index進(jìn)行改造娄周,通過如下算法對(duì)index賦值:

  • 每個(gè)ViewGroup下的所有View作為一個(gè)數(shù)組涕侈,從0開始;

  • 每個(gè)ViewGroup下的所有View先按照Class分類煤辨,然后再把每個(gè)類型中的數(shù)據(jù)按照數(shù)組的方式裳涛,從0開始;

  • 每個(gè)ViewGroup下的所有View先按照Class分類众辨,再確認(rèn)是否有Resource Id端三,如果存在,則index為0鹃彻,否則index為所屬Class類型數(shù)組下的序號(hào)郊闯。

該優(yōu)化處理對(duì)所有View適用。優(yōu)化后效果如下:即動(dòng)態(tài)改變一些控件后,只會(huì)影響同類型的控件团赁,其他類型控件的index不受影響育拨,也即ViewID不受影響。

(2) 可復(fù)用View

先來看一個(gè)應(yīng)用場景:

如圖所示欢摄,當(dāng)ListView上滑時(shí)熬丧,屏幕下方即將顯示的<元素6>其實(shí)復(fù)用了屏幕上方即將滑出的<元素0>,也就是說<元素6>與<元素0>的index均為0剧浸,在這種情況下锹引,我們無法通過前述index的定義來區(qū)分這兩個(gè)列表Item。

所幸唆香,針對(duì)這種情況,我們可以用position的取值進(jìn)行區(qū)分吨艇,也就是令index = position躬它。

通過實(shí)踐發(fā)現(xiàn),發(fā)生上述復(fù)用情形的View主要有以下幾類:AdapterView东涡、RecyclerView和ViewPager冯吓,其api都提供了獲取position的接口。

a. AdapterView

AdapterView的派生類均可通過getPositionForView獲取position疮跑。

index = position = ((AdapterView) group).getPositionForView(child);

作為AdapterView的派生類之一组贺,ExpandableListView因?yàn)樯婕暗絞roupPosition和childPosition,因此需要特殊處理祖娘。在構(gòu)造ViewID時(shí)失尖,將能夠采集到的position信息都添加到View Path中,具體策略如下:

  • 先將ExpandableListView作為普通AdapterView計(jì)算position

  • 列表Item為header元素渐苏,View Path中添加[header:position]

  • 列表Item為footer元素掀潮,footer的index需要額外計(jì)算,計(jì)算公式如下琼富,View Path中添加[footer:footerIndex]

    // Calculates the footer index among footers; 
    // For instance, there are five footers, so the footer index ranges from zero to four.
    // The first footer index is zero.
    footerIndex = position - (expandableListView.getCount() - expandableListView.getFooterViewsCount());
    
  • 列表Item為組元素仪吧,View Path中添加[group:groupPosition]

  • 列表Item為組內(nèi)元素,View Path中添加[group:groupPosition,child:childPosition]

涉及到的api接口如下:

((AdapterView) expandableListView).getPositionForView();
public long getExpandableListPosition(int flatListPosition);
public static int getPackedPositionType(long packedPosition);
public static int getPackedPositionGroup(long packedPosition);
public static int getPackedPositionChild(long packedPosition);

示例如下:

b. V7-RecyclerView

RecyclerView的情形比較簡單鞠眉,可通過調(diào)用getChildPositiongetChildAdapterPosition獲取position薯鼠。

@Deprecated
public int getChildPosition(View child);

public int getChildAdapterPosition(View child);

c. V4 - ViewPager

V4 - ViewPager可通過調(diào)用getCurrentItem獲取position。

public int getCurrentItem();

(3) Fragment節(jié)點(diǎn)

主流App的主頁均是采用如圖所示的Tab切換Fragment的設(shè)計(jì)械蹋。在這種情形下出皇,如果主頁內(nèi)嵌的Fragment采用“懶加載”方案,則底部Tab的點(diǎn)擊順序決定了該Tab對(duì)應(yīng)Fragment的初始化順序朝蜘,從而導(dǎo)致Fragment所屬父組件的index動(dòng)態(tài)變化恶迈。

也就是說,F(xiàn)ragment初始化順序影響ViewID。而前述Index優(yōu)化方案并不能解決這一問題暇仲。

Fragment節(jié)點(diǎn)特殊處理

針對(duì)Fragment初始化順序影響ViewID的問題步做,我們采用的解決方案是:

如果能夠獲取到Fragment實(shí)例的類名,則使用Fragment實(shí)例的類名替換View Path中的Fragment奈附,并設(shè)置[index]為特殊標(biāo)記[-]全度。例如:使用控件篇Tab對(duì)應(yīng)的Fragment實(shí)例ControlSetFragment以及特殊標(biāo)記[-]替換原View Path中的Fragment[3]

如何獲取Fragment實(shí)例?

采用代碼埋點(diǎn)或后續(xù)即將講到的插件埋點(diǎn)斥滤,在Fragment各實(shí)例類中重載下面的幾個(gè)方法将鸵,并在各方法中插入SDK提供的方法調(diào)用,從而實(shí)現(xiàn)Fragment生命周期監(jiān)聽:

@Override
public void onResume() {
    super.onResume();
    DATracker.getInstance().onFragmentResume(this);
}

@Override
public void onPause() {
    super.onPause();
    DATracker.getInstance().onFragmentPause(this);
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    DATracker.getInstance().setFragmentUserVisibleHint(this, isVisibleToUser);
}

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    DATracker.getInstance().onFragmentHiddenChanged(this, hidden);
}

通過上述調(diào)用佑颇,當(dāng)Fragment生命周期變化時(shí)顶掉,SDK能夠記錄當(dāng)前活躍的所有Fragment。當(dāng)某個(gè)活躍的Fragment上的控件被點(diǎn)擊了挑胸,SDK構(gòu)造該控件的ViewID時(shí)痒筒,會(huì)自動(dòng)將該Fragment實(shí)例的類名寫入View Path。

V4 - ViewPager內(nèi)嵌Fragment

這里要說明的是茬贵,ViewPager內(nèi)嵌的View不僅是可復(fù)用的簿透,同時(shí),由于其“懶加載”解藻、“預(yù)加載”機(jī)制老充,其內(nèi)嵌View的加載順序也是動(dòng)態(tài)的。特別地螟左,當(dāng)ViewPager內(nèi)嵌Fragment時(shí)啡浊,按照前述對(duì)Fragment節(jié)點(diǎn)的處理,我們會(huì)使用Fragment實(shí)例的類名替換View Path中的Fragment路狮,并設(shè)置[index]為特殊標(biāo)記[-]虫啥。之所以將[index]設(shè)置為特殊標(biāo)記[-],是因?yàn)镕ragment動(dòng)態(tài)加載導(dǎo)致index不可靠奄妨,而ViewPager中內(nèi)嵌的Fragment卻可以調(diào)用ViewPager的getCurrentItem拿到position作為index涂籽,這種情況下,是可以將index的值添加到View Path中的砸抛。

2.2 無埋點(diǎn)實(shí)現(xiàn)

通過前述方案评雌,我們可以使用ViewID唯一地標(biāo)識(shí)屏幕上的控件。那么直焙,比如一個(gè)Button景东,當(dāng)這個(gè)Button被點(diǎn)擊了,SDK又是如何捕捉到這一點(diǎn)擊事件奔誓,并且拿到Button實(shí)例的呢斤吐,也就是如何實(shí)現(xiàn)自動(dòng)埋點(diǎn)的呢搔涝?這里,我們提供了兩種方案和措。

2.2.1 代理監(jiān)聽

原理

在應(yīng)用程序中庄呈,輔助功能事件是用戶與可視界面組件交互的消息。這些消息是由輔助功能服務(wù)處理派阱。輔助功能服務(wù)使用在這些事件中的信息產(chǎn)生附加的反饋和提示诬留。Android 4.0(API14)及更高版本上,輔助功能方法屬于View類的一部分贫母,也是View.AccessibilityDelegate的一部分文兑。其中可用于實(shí)現(xiàn)無埋點(diǎn)的方法如下:

sendAccessibilityEvent()

當(dāng)用戶在一個(gè)視圖上操作時(shí)調(diào)用此方法。事件按照用戶操作類型分類腺劣,涵蓋以下事件類型:

  • TYPE_VIEW_CLICKED
  • TYPE_VIEW_LONG_CLICKED
  • TYPE_VIEW_FOCUSED
  • TYPE_VIEW_SELECTED
  • TYPE_VIEW_HOVER_ENTER
  • TYPE_VIEW_SCROLLED
  • TYPE_VIEW_TEXT_CHANGED
  • ...

采用輔助功能事件實(shí)現(xiàn)無埋點(diǎn)绿贞,簡單來講,就是給View設(shè)置AccessibilityDelegate橘原,當(dāng)View產(chǎn)生了click樟蠕,long_click等事件時(shí),會(huì)在響應(yīng)原有的Listener方法后靠柑,發(fā)送消息給AccessibilityDelegate,然后在sendAccessibilityEvent方法下搜集自動(dòng)埋點(diǎn)事件吓懈。

        private class TrackingAccessibilityDelegate extends View.AccessibilityDelegate {

            public TrackingAccessibilityDelegate(ViewNode viewNode, View.AccessibilityDelegate realDelegate) {
                mViewNode = viewNode;
                mRealDelegate = realDelegate;
            }

            public View.AccessibilityDelegate getRealDelegate() {
                return mRealDelegate;
            }

            @Override
            public void sendAccessibilityEvent(View host, int eventType) {
                if (eventType == mEventType && host == mViewNode.getView()) {
                        ...
                        // 自動(dòng)埋點(diǎn)
                    fireEvent(mViewNode, type);// sends tracking data
                }

                    // 響應(yīng)原AccessibilityDelegate
                if (null != mRealDelegate) {
                    mRealDelegate.sendAccessibilityEvent(host, eventType);
                }
            }

            private View.AccessibilityDelegate mRealDelegate;
            private ViewNode mViewNode;
        }

設(shè)置代理的時(shí)機(jī)

實(shí)現(xiàn)Application.ActivityLifecycleCallbacks歼冰,用來監(jiān)聽Activity生命周期,當(dāng)監(jiān)聽到某個(gè)Activity進(jìn)入onResumed狀態(tài)時(shí)耻警,通過以下方式獲取RootView:

mViewRoot = this.mActivity.getWindow().getDecorView().getRootView()

從RootView出發(fā)深度優(yōu)先遍歷控件樹隔嫡,為滿足特定條件的View設(shè)置代理監(jiān)聽。

界面動(dòng)態(tài)變化怎么辦甘穿?

實(shí)現(xiàn)ViewTreeObserver.OnGlobalLayoutListener腮恩,用來監(jiān)聽界面變化。當(dāng)監(jiān)聽到界面變化時(shí)温兼,重新遍歷控件樹秸滴,為滿足特定條件的View設(shè)置代理監(jiān)聽,已經(jīng)設(shè)置過代理的View不再重復(fù)設(shè)置募判。

界面的監(jiān)測(cè)操作需要放在界面主線程中荡含,起初我們擔(dān)心這樣會(huì)對(duì)應(yīng)用本身的界面交互產(chǎn)生影響,所幸届垫,經(jīng)過實(shí)際測(cè)試释液,這樣實(shí)現(xiàn)是可行的,界面交互感知不到任何影響装处。

監(jiān)控哪些View?

  • AutoCompleteTextView(搜索框)

    添加 TextWatcher 監(jiān)聽文本變化误债,2s 后延時(shí)發(fā)送文本輸入結(jié)果

  • AbsListView(列表)

    OnItemClickListener 存在 - 對(duì)原有OnItemClickListener作一層包裝,在響應(yīng)原有的Listener方法后,搜集自動(dòng)埋點(diǎn)事件寝蹈。

  • 一般View

    hasOnClickListeners 或 isClickable 返回 true - 設(shè)置AccessibilityDelegate

2.2.2 gradle插件

原理

試想一下我們代碼埋點(diǎn)的過程:首先定位到事件響應(yīng)函數(shù)李命,例如Button的onClick函數(shù),然后在該事件響應(yīng)函數(shù)中調(diào)用SDK數(shù)據(jù)搜集接口躺盛。下面项戴,我們介紹使用gradle插件自動(dòng)在目標(biāo)響應(yīng)函數(shù)中插入SDK數(shù)據(jù)搜集代碼,達(dá)到自動(dòng)埋點(diǎn)的目的槽惫。

我們的gradle插件采用 Android gradle 插件提供的最新的Transform API周叮,在Apk編譯環(huán)節(jié)中、class打包成dex之前界斜,插入了中間環(huán)節(jié)仿耽,調(diào)用 ASM API對(duì)class文件的字節(jié)碼進(jìn)行掃描,當(dāng)掃描到目標(biāo)事件響應(yīng)函數(shù)時(shí)各薇,在函數(shù)頭部或尾部插入SDK數(shù)據(jù)搜集代碼项贺。

監(jiān)控哪些View?

我們?cè)谀繕?biāo)View的事件響應(yīng)函數(shù)中插入SDK數(shù)據(jù)搜集代碼,即可實(shí)現(xiàn)對(duì)該類型View的監(jiān)控峭判。例如开缎,在Button的點(diǎn)擊事件響應(yīng)函數(shù)onClick中插入SDK數(shù)據(jù)搜集代碼后,當(dāng)Button被點(diǎn)擊林螃,便會(huì)執(zhí)行到onClick中的SDK數(shù)據(jù)搜集代碼奕删,從而實(shí)現(xiàn)Button點(diǎn)擊事件的自動(dòng)搜集。

目標(biāo)事件響應(yīng)函數(shù)(方法):

  • onClick(Landroid/view/View;)V
  • onClick(Landroid/content/DialogInterface;I)V
  • onItemClick(Landroid/widget/AdapterView;Landroid/view/View;IJ)V
  • onItemSelected(Landroid/widget/AdapterView;Landroid/view/View;IJ)V
  • onGroupClick(Landroid/widget/ExpandableListView;Landroid/view/View;IJ)Z
  • onChildClick(Landroid/widget/ExpandableListView;Landroid/view/View;IIJ)Z
  • onRatingChanged(Landroid/widget/RatingBar;FZ)V
  • onStopTrackingTouch(Landroid/widget/SeekBar;)V
  • onCheckedChanged(Landroid/widget/CompoundButton;Z)V
  • onCheckedChanged(Landroid/widget/RadioGroup;I)V
  • ...

具體實(shí)現(xiàn):

  • 對(duì)app中指定包進(jìn)行掃描疗认,篩選出實(shí)現(xiàn)了目標(biāo)接口的類完残,在目標(biāo)方法中添加數(shù)據(jù)采集代碼。

例如横漏,篩選出實(shí)現(xiàn)了android/view/View$OnClickListener接口的類谨设,然后在onClick(Landroid/view/View;)V方法中注入采集數(shù)據(jù)的代碼。

目標(biāo)效果:

public class MainActivity extends AppCompatActivity implements OnClickListener, 
    android.content.DialogInterface.OnClickListener, 
    OnItemClickListener, 
    OnItemSelectedListener, 
    OnRatingBarChangeListener, 
    OnSeekBarChangeListener, 
    OnCheckedChangeListener, 
    android.widget.RadioGroup.OnCheckedChangeListener, 
    OnGroupClickListener, OnChildClickListener {

    public void onClick(View var1) {
        PluginAgent.onClick(var1);
    }

    public void onClick(DialogInterface var1, int var2) {
        PluginAgent.onClick(this, var1, var2);
    }

    public void onItemClick(AdapterView<?> var1, View var2, int var3, long var4) {
        PluginAgent.onItemClick(this, var1, var2, var3, var4);
    }
    ...
}

Fragment生命周期追蹤

在ViewID優(yōu)化中缎浇,我們講到Fragment節(jié)點(diǎn)的優(yōu)化時(shí)扎拣,提到可通過重寫Fragment的幾個(gè)與生命周期相關(guān)的函數(shù)監(jiān)聽Fragment生命周期。這個(gè)過程除了使用代碼埋點(diǎn)华畏,也可借助插件自動(dòng)完成:掃描class文件鹏秋,定位Fragment的幾個(gè)與生命周期相關(guān)的函數(shù),自動(dòng)插入代碼亡笑。

目標(biāo)函數(shù)(方法):

  • onResume()V
  • onPause()V
  • setUserVisibleHint(Z)V
  • onHiddenChanged(Z)V

具體實(shí)現(xiàn):

  • 對(duì)app中指定包進(jìn)行掃描侣夷,篩選出所有父類為下列其中之一的子類。以下是Fragment及系統(tǒng)內(nèi)置的幾個(gè)常見的Fragment派生類仑乌。

    android/app/Fragment
    android/app/DialogFragment
    android/app/ListFragment
    android/support/v4/app/Fragment
    android/support/v4/app/DialogFragment
    android/support/v4/app/ListFragment
    
  • 對(duì)這些Fragment子類的onResumed百拓,onPaused琴锭,onHiddenChangedsetFragmentUserVisibleHint方法的字節(jié)碼進(jìn)行修改衙传,添加數(shù)據(jù)采集代碼决帖。

目標(biāo)效果:

public class BaseFragment extends Fragment {
    public BaseFragment() {
    }

    public void onResume() {
        super.onResume();
        PluginAgent.onFragmentResume(this);
    }

    public void onHiddenChanged(boolean var1) {
        super.onHiddenChanged(var1);
        PluginAgent.onFragmentHiddenChanged(this);
    }

    public void onPause() {
        super.onPause();
        PluginAgent.onFragmentPause(this);
    }

    public void setUserVisibleHint(boolean var1) {
        super.setUserVisibleHint(var1);
        PluginAgent.setFragmentUserVisibleHint(this, var1);
    }
}

2.2.3 代理監(jiān)聽 vs gradle插件

插件埋點(diǎn)方案,發(fā)生在編譯期蓖捶,當(dāng)目標(biāo)事件響應(yīng)函數(shù)被執(zhí)行時(shí)地回,才會(huì)觸發(fā)我們插入的代碼主動(dòng)搜集事件。除了消耗一點(diǎn)編譯速度俊鱼,應(yīng)用運(yùn)行期間基本不受影響刻像。

代理監(jiān)聽方案,由于事先并不清楚用戶會(huì)觸發(fā)哪些交互事件并闲,所以需要為所有可交互的View設(shè)置代理细睡,涉及到控件樹遍歷,因此性能略遜于gradle插件方案帝火。但好在控件樹遍歷消耗的時(shí)間是毫秒級(jí)的溜徙,不會(huì)影響界面交互。

下面總結(jié)一下這兩種方案的優(yōu)缺點(diǎn)犀填。

(1) 代理監(jiān)聽方案

缺點(diǎn):

  • 遍歷蠢壹,被動(dòng)等待被觸發(fā)
  • 攔截彈窗比較困難
  • Fragment生命周期需手動(dòng)攔截

優(yōu)點(diǎn):

  • 對(duì)于可點(diǎn)擊但又未設(shè)置點(diǎn)擊監(jiān)聽器的View,可設(shè)置監(jiān)聽器

(2) gradle插件方案

優(yōu)點(diǎn):

  • 無需遍歷九巡,主動(dòng)觸發(fā)事件
  • 主動(dòng)攔截彈窗(待擴(kuò)展)

缺點(diǎn):

  • 目前只支持Gradle1.5+構(gòu)建工具

3 總結(jié)與展望

以上就是網(wǎng)易HubbleData在Android端的無埋點(diǎn)實(shí)踐中總結(jié)的重點(diǎn)難點(diǎn)知残。還有一些邊邊角角的點(diǎn)就不一一細(xì)述了。

當(dāng)然比庄,我們的無埋點(diǎn)方案也并不完美,還有一些未解決的問題乏盐。例如佳窑,ViewID的構(gòu)造及優(yōu)化方案并不能適用于所有情況;通過無埋點(diǎn)搜集的數(shù)據(jù)也僅限控件的一些固有屬性父能,并沒有搜集到更有價(jià)值的業(yè)務(wù)數(shù)據(jù)...

網(wǎng)易HubbleData也將持續(xù)跟進(jìn)業(yè)界先進(jìn)埋點(diǎn)技術(shù)神凑,及時(shí)升級(jí)埋點(diǎn)方案。后續(xù)針對(duì)比較有意思的技術(shù)點(diǎn)何吝,也會(huì)繼續(xù)整理出來分享給大家溉委。

如果對(duì)該項(xiàng)目感興趣,可以聯(lián)系 zhangdan_only@163.com 爱榕,歡迎一起研究瓣喊。

預(yù)知更多,請(qǐng)猛戳??
用于Android客戶端無埋點(diǎn)數(shù)據(jù)采集的Gradle插件
網(wǎng)易HubbleData無埋點(diǎn)SDK在iOS端的設(shè)計(jì)與實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末黔酥,一起剝皮案震驚了整個(gè)濱河市藻三,隨后出現(xiàn)的幾起案子洪橘,更是在濱河造成了極大的恐慌,老刑警劉巖棵帽,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熄求,死亡現(xiàn)場離奇詭異,居然都是意外死亡逗概,警方通過查閱死者的電腦和手機(jī)弟晚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逾苫,“玉大人卿城,你說我怎么就攤上這事×タ澹” “怎么了藻雪?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狸吞。 經(jīng)常有香客問我勉耀,道長,這世上最難降的妖魔是什么蹋偏? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任便斥,我火速辦了婚禮,結(jié)果婚禮上威始,老公的妹妹穿的比我還像新娘枢纠。我一直安慰自己,他們只是感情好黎棠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布晋渺。 她就那樣靜靜地躺著,像睡著了一般脓斩。 火紅的嫁衣襯著肌膚如雪木西。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天随静,我揣著相機(jī)與錄音八千,去河邊找鬼。 笑死燎猛,一個(gè)胖子當(dāng)著我的面吹牛恋捆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播重绷,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼沸停,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了昭卓?” 一聲冷哼從身側(cè)響起星立,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤爽茴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后绰垂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體室奏,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年劲装,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胧沫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡占业,死狀恐怖绒怨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谦疾,我是刑警寧澤南蹂,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站念恍,受9級(jí)特大地震影響六剥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜峰伙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一疗疟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞳氓,春花似錦策彤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至音榜,卻和暖如春必搞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背囊咏。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留塔橡,地道東北人梅割。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像葛家,于是被迫代替她去往敵國和親户辞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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