版權(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)用getChildPosition
和getChildAdapterPosition
獲取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
琴锭,onHiddenChanged
,setFragmentUserVisibleHint
方法的字節(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)