Android端可視化埋點的實現(xiàn)
***導(dǎo)語 ***客戶端埋點是數(shù)據(jù)收集的最基本手段奖慌,對于一款A(yù)PP來說星瘾,代碼埋點(就是在業(yè)務(wù)代碼中组力,在需要埋點的view的點擊事件回調(diào)處做點擊上報的處理瘟斜,當(dāng)此view被點擊時县貌,進(jìn)行相應(yīng)事件的上報)是最為常見的埋點方式术陶,但由于業(yè)務(wù)迭代速度很快,手動埋點方案雖然靈活多變煤痕,但是極大的增加了客戶端開發(fā)人員的工作量梧宫。開發(fā)完成業(yè)務(wù)功能需要花費很大的精力處理埋點事宜接谨,而且隨著迭代版本,新的需求增加塘匣,埋點的數(shù)量會越來越多脓豪,這些老舊埋點的維護(hù)工作也需要付出不小的努力。并且忌卤,代碼埋點需要跟版本迭代扫夜,如果在開發(fā)過程中忘記埋點,就只能等待下個版本再埋點驰徊。所以笤闯,如果能夠?qū)崿F(xiàn)不需要或者很少需要開發(fā)人員介入就能實現(xiàn)根據(jù)不同業(yè)務(wù)場景埋點的功能對于提高版本迭代速度和開發(fā)人員的幸福感絕對是一件非常有價值的事情。 所以棍厂,為了減少代碼埋點的不便颗味,不需要開發(fā)人員介入,使運營或者用研的同學(xué)就可以隨時動態(tài)調(diào)整需要上報的點牺弹,我們需要改變下當(dāng)前的埋點方式浦马。
一、需求背景
1张漂、什么是埋點
預(yù)先在目標(biāo)應(yīng)用采集數(shù)據(jù)晶默,對特定用戶行為或事件進(jìn)行捕獲、處理以一定方式上報至服務(wù)器的相關(guān)技術(shù)及其實施過程鹃锈。
在大數(shù)據(jù)話的今天荤胁,一個好的產(chǎn)品就應(yīng)該符合用戶的需求,我們做統(tǒng)計就是收集用戶的行為屎债,分析用戶的喜好仅政,不斷的根據(jù)用戶行為改進(jìn)我們的產(chǎn)品,當(dāng)然專業(yè)的需要數(shù)據(jù)分析來完成盆驹,我們需要的就是為分析提供用戶數(shù)據(jù)圆丹。
原來我們的方式是代碼埋點,在每一個要上報的view的點擊事件都都要由開發(fā)人員加入上報的代碼躯喇。
public void onClick(View v) {
..
..
//在點擊事件回調(diào)中走統(tǒng)一的上報
ClickReport.collect(ClickReport.MineAction.MINE_MORE_PRIVILEGE);
..
..
}
復(fù)制代碼
2辫封、存在的問題
但由于業(yè)務(wù)迭代速度很快,手動埋點方案雖然靈活多變廉丽,但是也存在很多問題:
1倦微、開發(fā)完成業(yè)務(wù)功能需要花費很大的精力處理埋點事宜,而且多余的代碼顯得很冗余正压,浪費開發(fā)時間
2欣福、隨著迭代版本,新的需求增加焦履,埋點的數(shù)量會越來越多拓劝,這些老舊埋點的維護(hù)工作也需要付出不小的努力雏逾。
3、代碼埋點需要跟版本迭代郑临,無法實現(xiàn)動態(tài)化的配置栖博,如果在開發(fā)過程中忘記埋點,就只能等待下個版本再埋點厢洞。
所以仇让,為了減少代碼埋點的不便,減少開發(fā)人員介入犀变,使運營或者用研的同學(xué)就可以隨時動態(tài)化的調(diào)整需要上報的點妹孙,我們需要改變下當(dāng)前的埋點方式,開發(fā)一個合適客戶端埋點的上報模式获枝。
二蠢正、可視化埋點
1、埋點的三種方式:
因為不同的app也會有自己的異化處理省店,埋點的方式也是根據(jù)特殊情況有這眾多方案的嚣崭,但是大體上現(xiàn)在主要流行的就是三種方案:
1、代碼埋點:將收集數(shù)據(jù)的代碼直接寫在需要的地方懦傍,當(dāng)用戶點擊某個控件或者打開某個頁面時調(diào)用到該部分代碼完成數(shù)據(jù)的收集雹舀。
- 優(yōu)點:準(zhǔn)確性高,收集數(shù)據(jù)和發(fā)送數(shù)據(jù)都能精確控制粗俱,同時能方便的設(shè)置自定義屬性说榆,自定義控件,自定義View等寸认。
- 缺點:埋點工作量大签财,更新代價大,需要跟版本偏塞,不靈活唱蒸。
2、可視化埋點:根據(jù)可視化界面進(jìn)行配置然后上報后臺灸叼,后臺向終端下發(fā)配置文件神汹,終端點擊時獲取當(dāng)前點擊的控件根據(jù)配置文件進(jìn)行選擇上報。
- 優(yōu)點:數(shù)據(jù)量相對精確古今,覆蓋范圍大屁魏。
- 缺點:可視化平臺搭建困難,控件樹的元素的精確識別較為麻煩捉腥。
3氓拼、無埋點:與可視化埋點基本一致。不同點在于可視化埋點是根據(jù)配置文件收集數(shù)據(jù),無埋點是預(yù)先收集所有的用戶行為披诗,然后根據(jù)配置文件來提取數(shù)據(jù)。
- 優(yōu)點:數(shù)據(jù)覆蓋全立磁,開發(fā)人員工作量小呈队。
- 缺點:數(shù)據(jù)量大,后端篩選分析工作量大唱歧,每個點都統(tǒng)計對性能有隱患宪摧。
2、為什么選擇可視化的埋點
對于無埋點颅崩,和可視化埋點的方案很相似几于,區(qū)別在于無埋點需要將所以的點擊都收集,后續(xù)在根據(jù)配置文件在分析想要的數(shù)據(jù)沿后,這種方式對于客戶端來說很省事沿彭,客戶端也不用分析是哪個點,只要用戶點擊了就上報尖滚,但是對于后端的篩選和分析一定的影響喉刘,而且可能我們需要統(tǒng)計的點并不是很多時可能大多數(shù)上報的點都不是我們想要的,這時用無埋點的方式感覺有些得不償失漆弄。
MTA也有一個可視化埋點的功能睦裳,但是咱們我們要上報的平臺有很多,比如要上報到羅盤或者我們自己服務(wù)器的統(tǒng)計平臺撼唾,僅僅使用MTA的sdk無法滿足各種平臺的上報廉邑,所以我們選擇可視化埋點的方式統(tǒng)計用戶的行為,并且在統(tǒng)一的模塊進(jìn)行各個平臺的上報倒谷。
三蛛蒙、可視化埋點方案探討與具體實現(xiàn)
1、方案介紹
主要的流程可以分為配置埋點模式恨锚,和用戶模式宇驾。
上報的信息:
{
String ViewId; //當(dāng)前View的唯一ID 由路徑獲得
String EventId; //View的事件ID 由產(chǎn)品相關(guān)人員配置
}
復(fù)制代碼
在配置模式中,由產(chǎn)品同學(xué)或者運營同學(xué)配置需要上報的點擊view猴伶,將點擊view的唯一標(biāo)識viewID和事先定好的用于分析的eventID綁定形成一個配置map表课舍,配置結(jié)束后,將已經(jīng)配置完成的map表上傳至后臺他挎,完成配置模式筝尾。
[圖片上傳失敗...(image-4aaf40-1649852259824)]
<figcaption style="display: block;">圖1:可視化埋點流程</figcaption>
在配置模式中,由產(chǎn)品同學(xué)或者運營同學(xué)配置需要上報的點擊view办桨,將點擊view的唯一標(biāo)識viewID和事先定好的用于分析的eventID綁定形成一個配置map表筹淫,配置結(jié)束后,將已經(jīng)配置完成的map表上傳至后臺呢撞,完成配置模式损姜。
在用戶模式下饰剥,就是當(dāng)用戶開啟app時,會拉取到在產(chǎn)品或者運營同學(xué)在配置模式下配置的map表摧阅,然后當(dāng)用戶點擊相應(yīng)點時汰蓉,可以通過某種方式與配置表中的點做對照,如果在配置表中棒卷,就走上報邏輯顾孽。
通過上述流程,可以總結(jié)出以下三個問題:
- 如何獲取當(dāng)前點擊的view比规;
- 如何確定view的唯一標(biāo)識viewID若厚;
- 用戶點擊view的上報流程;
下面針對這三個問題進(jìn)行分析和方案的確定蜒什。
2测秸、獲取當(dāng)前點擊的view
因為我門需要在一個統(tǒng)一的地方獲取當(dāng)前的點擊事件,來做統(tǒng)一的配置或者篩選上報工作灾常,這些工作不可能在每一個view的點擊事件回調(diào)中完成乞封,比較普遍的做法就是遍歷當(dāng)前activity下的viewtree,根據(jù)UI布局的特性和Android點擊事件傳遞機(jī)制來找到當(dāng)前的view岗憋。
主流的方案:通過位置遍歷計算
讓項目中主框架的BaseFragmentActivity基類重寫Activity的dispatchTouchEvent方法肃晚,當(dāng)touch button時,可以獲取到按下(DOWN)和抬起(UP)時產(chǎn)生的MotionEvent對象仔戈。這個MotionEvent對象有兩個方法关串,getRawX()和getRawY(),通過這兩個方法我們可以獲取到“點擊位置”在界面中的坐標(biāo)监徘。通過rootview可以層層遍歷其下的子view以及所有子View上的控件晋修,這些View和控件在屏幕中的坐標(biāo)和寬高我們是可以獲取到的。然后搜索所有的子View或者控件的布局區(qū)域是否包含“點擊位置”凰盔,從而來判斷哪個View或控件被點擊墓卦。
/**
* 通過遍歷的方式獲取當(dāng)前view
*/
private View searchClickView(View view, MotionEvent event,StringBuffer stringBuffer) {
..
..
if (isInView(view, event) && view.getVisibility() == View.VISIBLE) { //這里一定要判斷View是可見的
if (view instanceof ViewGroup) { //遇到一些Layout之類的ViewGroup,繼續(xù)遍歷它下面的子View
ViewGroup group = (ViewGroup) view;
for (int i = group.getChildCount() - 1; i >= 0; i--) {
View chilView = group.getChildAt(i);
clickView = searchClickView(chilView, event,stringBuffer);
if (clickView != null) return clickView;
}
}
}
..
..
}
復(fù)制代碼
但是這種獲取view的方式畢竟是靠遍歷來獲得的户敬,難免對性能有一些影響落剪,其實通過viewGroup的TouchTarget的類型可以有一種更好的方式去得到當(dāng)前的view。
我們的方案:通過TouchTarget類型獲取
ViewGroup中有一個TouchTarget 類型的變量 mFirstTouchTarget尿庐,表示消費當(dāng)前觸摸事件的控件列表忠怖。例如,點擊屏幕上一個按鈕抄瑟,那么按鈕所在ViewGroup的mFirstTouchTarget 變量就指向這個按鈕凡泣。當(dāng)ViewGroup派發(fā)觸摸事件時,他會首先判斷變量mFirstTouchTarget是否存在,如果變量存在鞋拟,會循環(huán)遍歷TouchTarget鏈表元素骂维,找到能處理該事件的View并將MotionEvent 派發(fā)給該View。如果不存在TouchTarget贺纲,ViewGroup 會循環(huán)遍歷所有child view席舍,直到找到一個能處理該事件的View,并將該View作為first touch target 賦值給mFirstTouchTarget哮笆。 當(dāng)用戶觸發(fā)Down事件時,會執(zhí)行如下邏輯汰扭,尋找消費當(dāng)前事件的TouchTarget稠肘。
if (actionMasked == MotionEvent.ACTION_DOWN){
//如果是down事件,遍歷child萝毛,找到TouchTarget
..
..
final View[] children = mChildren;
..
..
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// child 消費了觸摸事件
..
..
// 根據(jù)消費了觸摸事件的View創(chuàng)建TouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
..
..
break;
}
}
復(fù)制代碼
addTouchTarget就是獲取子view的touchTarget项阴,并將其加入到TouchTarget鏈的最頂部,因為是從獲取到DOWN事件的view層層向上遞歸笆包,所以TouchTarget鏈的尾端就是目標(biāo)view环揽。
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
復(fù)制代碼
當(dāng)觸發(fā)Down事件并且找到TouchTarget,或者觸發(fā)非Down事件時庵佣,執(zhí)行如下處理邏輯歉胶。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
//Down事件發(fā)生時找到TouchTarget,或者非Down事件直接執(zhí)行如下邏輯
// 將事件派發(fā)給TouchTarget表示的View
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,target.child,target.pointerIdBits)) {
//指定TouchTarget對應(yīng)的View正確消費了事件
handled = true;
}
..
..
}
..
..
}
}
復(fù)制代碼
因為根據(jù)view的點擊事件可知巴粪,view點擊時事件是從根節(jié)點開始向下進(jìn)行傳遞通今,如果viewgroup存在TouchTarget,會從TouchTarget的成員變量中獲取當(dāng)前的處理事件的view或者viewgroup肛根,如果該viewgroup存在TouchTarget辫塌,就繼續(xù)向下查找,直到當(dāng)前的viewgroup的TouchTarget為空時派哲,就說明是此viewgroup消費了這個事件臼氨;或者直到傳遞到一個view而不是viewgroup時,此view就是當(dāng)前用戶點擊的view芭届。
因為mFirstTouchTarget是一個TouchTarget類型的私有成員變量储矩,我們需要通過反射的方式去獲取:
/**
* 獲取當(dāng)前點擊的view
*/
public final View getTouchTarget(final ViewGroup vg) {
..
..
while (currentTarget != null) {
Field fieldTouchTarget = ReflectHelper.getDeclaredField(ViewGroup.class, "mFirstTouchTarget");
Object touchTarget = ReflectHelper.getFieldValue(fieldTouchTarget, currentTarget);
if (touchTarget == null) {
break;
}
Field fieldChild = ReflectHelper.getDeclaredField(touchTarget.getClass(), "child");
View child = (View) ReflectHelper.getFieldValue(fieldChild, touchTarget);
if (child instanceof ViewGroup) {
preTarget = currentTarget;
currentTarget = child;
continue;
} else if (child instanceof View) {
currentTarget = child;
break;
}
}
..
..
return currentTarget;
}
復(fù)制代碼
利用ViewGroup的這種事件處理機(jī)制褂乍,可以在activity的dispatchTouchEvent中添加處理邏輯椰苟,如果接收到down事件,就讓其傳遞下去使mFirstTouchTarget被賦值树叽,當(dāng)接收到up事件時舆蝴,做相應(yīng)的獲取當(dāng)前view邏輯,通過rootview的mFirstTouchTarget獲取其子view,如果子view的mFirstTouchTarget不為空洁仗,就通過這種TouchTarget的鏈?zhǔn)疥P(guān)系獲得這次點擊行為的最終view层皱。
3、唯一的標(biāo)識一個view
根據(jù)上述流程赠潦,用戶模式下叫胖,當(dāng)用戶點擊某個view時,需要一個唯一的viewID用來在配置表中進(jìn)行查找她奥,并且需要與其他的view做區(qū)分瓮增;在配置模式下也需要通過唯一標(biāo)示的viewID作為key與作為value的eventID共同上報到配置表中,那么應(yīng)該如何選取這個唯一的標(biāo)示呢哩俭?
主流的方案:遍歷viewTree生成路徑
對于view來說绷跑,Android系統(tǒng)提供了一個ID,view.getId()即可獲得一個int型的id用于區(qū)分View,但是這個ID因為以下兩個原因卻并不能滿足我們的需要凡资。
- 有相當(dāng)一部分view是NO_ID砸捏,比如在布局文件中未指定id,或者直接在代碼里面new出來view,view.getId()返回的全部都是NO_ID
- 這個ID是不穩(wěn)定的隙赁,由于這個ID其實就是每次編譯產(chǎn)生的R文件中的int常量垦藏,因此同一個按鈕,兩個版本編譯出來的ID很可能是不一樣的伞访。
所以為了使viewID不受版本和手機(jī)的影響掂骏,通常的方式就是利用所屬activity+viewtree的路徑形式來構(gòu)建viewID。
[圖片上傳失敗...(image-23560b-1649852259824)]
<figcaption style="display: block;">圖2:view層級示意圖</figcaption>
通過遍歷當(dāng)前activity的viewTree的方式厚掷,得到當(dāng)前view的父類芭挽、祖父類一直到根view,然后根據(jù)view的路徑層級關(guān)系再來確定viewID蝗肪。但是這又需要遍歷袜爪,我們不應(yīng)該每獲得一個view都要進(jìn)行遍歷,這樣也是比較繁瑣的薛闪。
我們的方案:通過TouchTarget鏈獲取
在第一個問題如果獲取當(dāng)前view時已經(jīng)說過辛馆,可以通過viewGroup的TouchTarget鏈來獲取當(dāng)前的點擊view,并且鏈上的層級關(guān)系就是從根view到父view再到子view的一個層級豁延,所以在獲取view的同時就可以順便記錄層級關(guān)系昙篙,來作為viewID。
可以在獲取當(dāng)前view的過程中诱咏,記錄每一個節(jié)點TouchTarget的mFirstTouchTarget苔可,這樣就可以在得到點擊view的同時,也得到相應(yīng)的view的路徑比如:DecorView-LinearLayout-FrameLayout-RelativeLayout-Button袋狞,再配置上view的class類名等信息焚辅,我們就可以同時確定目標(biāo)控件的唯一標(biāo)識映屋,將viewID通過setTag進(jìn)行設(shè)置,在需要的地方取出同蜻。所以在方案上我們還是用利用獲取viewgroup的TouchTarget來得到當(dāng)前點擊的view棚点,同時也可以根據(jù)這種鏈?zhǔn)疥P(guān)系來記錄點擊view的從父類到子view的路徑,從而將此路徑作為當(dāng)前view的唯一標(biāo)示viewID湾蔓。
4瘫析、用戶端的埋點上報流程
對于可視化埋點操作來說,在用戶模式下默责,當(dāng)用戶開啟app時進(jìn)行配置表拉取的操作贬循,然后當(dāng)用戶進(jìn)行點擊時,通過遍歷當(dāng)前activity下的viewtree獲得點擊的view桃序,或者通過上述分析的查找viewtree根視圖的TouchTarget鏈表的方式來獲取當(dāng)前的view杖虾,然后從配置表中查找出與點擊view的viewID一致的數(shù)據(jù),進(jìn)行上報葡缰。
主流的方案:view代理監(jiān)聽的方式
還有一種比較常用可視化埋點方式就是view的代理監(jiān)聽的方式,ActivityLifecycleCallbacks忱反,用來監(jiān)聽Activity生命周期泛释,當(dāng)activity被開啟時,遍歷當(dāng)前activity下的所有view温算,如果view在配置表中怜校,就設(shè)置當(dāng)前view的setAccessibilityDelegate。
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class VisualAnalysisManager extends View.AccessibilityDelegate{
..
..
view.setAccessibilityDelegate(this);
..
..
}
復(fù)制代碼
當(dāng)view產(chǎn)生了click或者long_click等點擊事件時注竿,分析View的源碼在處理點擊事件的回調(diào)時調(diào)用了 View.performClick 方法茄茁,內(nèi)部調(diào)用了sendAccessibilityEvent,
會在響應(yīng)原有的Listener方法后巩割,發(fā)送消息給AccessibilityDelegate裙顽,然后在繼承AccessibilityDelegate的類中重寫sendAccessibilityEvent方法來上報自動埋點事件。
public boolean performClick() {
..
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
..
}
public void sendAccessibilityEvent(int eventType) {
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
} else {
sendAccessibilityEventInternal(eventType);
}
}
復(fù)制代碼
代理監(jiān)聽的方式就是在ActivityLifecycleCallbacks監(jiān)聽到activity開啟時對viewtree進(jìn)行遍歷宣谈,如果遍歷view的viewID在后臺下發(fā)的配置表中愈犹,就設(shè)置setAccessibilityDelegate,這種方式比較常用闻丑,但也要求sdk版本大于14漩怎。
我們的方案:獲取viewID與配置表對比
用戶模式與配置模式都需要獲取當(dāng)前點擊的view,所以除類用上述view的代理監(jiān)聽的方式進(jìn)行上報嗦嗡,還可以與配置模式相同勋锤,用TouchTarget鏈表的方式來獲取當(dāng)前的view,再判斷當(dāng)前的點擊view是否需要上報侥祭。本需求在用戶模式下也是采用TouchTarget鏈表的方式獲取當(dāng)前view進(jìn)行上報叁执,與配置模式統(tǒng)一茄厘,方便開發(fā)也避免代理監(jiān)聽的方式在在sdk小于14無法使用的困擾。
在獲取view中徒恋,需要獲取當(dāng)前的activity蚕断,以及我們可能需要在當(dāng)前activity生命周期中去做一些設(shè)置以及初始化等操作,最簡單的方式是通過ActivityLifecycleCallbacks來監(jiān)聽activity的生命周期入挣。然后在回調(diào)中做相應(yīng)的操作亿乳,可以實現(xiàn)代碼解藕。
public class ActivityTrackUtils implements ActivityTracer.ActivityLifecycleCallbacks {
..
..
private ActivityTracer activityTracer;
public void start(Application app) {
activityTracer.install(app);
activityTracer.registerActivityLifecycleCallbacks(this);
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (callback != null) {
callback.onActivityCreated(activity, savedInstanceState);
}
..
..
復(fù)制代碼
四径筏、實現(xiàn)中遇到的問題以及解決方式
1葛假、兼容性問題上的優(yōu)化
上面第四章也說明了,獲取當(dāng)前view是通過反射的方式獲取viewGroup中的私有成員滋恬,這樣就可能因為不同手機(jī)api版本不同聊训,源碼會有一些差異,可能在某個版本的api中就無法獲得恢氯,雖然這樣的概率很小带斑,但是為了保證代碼的穩(wěn)定,提高魯棒性勋拟,還是可以提供一個降級方案:
public final View getTouchTarget(final ViewGroup vg,final MotionEvent event) {
..
..
//方案A
targetView = mVisualAnalysisHelper.getTouchTarget(vg);
//方案B
//如果mFirstTouchTarget的方式查詢不出來勋磕,走降級方案
if(targetView == null){
targetView = mVisualAnalysisHelper.getTouchTargetForPlanB(vg,event);
}
..
復(fù)制代碼
可以將一個問題中的獲取view的主流方法作為降級方案,通過遍歷當(dāng)前viewTree的形式敢靡,判斷當(dāng)前的坐標(biāo)是否在view的范圍之中挂滓,并循環(huán)遍歷viewGroup中的子view,最終可以獲取到當(dāng)前的點擊view啸胧。
2赶站、特殊控件的處理
有些控件可能會提前截取點擊事件單獨做處理,這樣事件無法下發(fā)纺念,導(dǎo)致無法獲取當(dāng)前的view贝椿,TouchTarget為null,比如微云項目中有個大標(biāo)題中的控件陷谱,在我查找了眾多原因后發(fā)現(xiàn)其實就是它在onInterceptTouchEvent 方法中自己做了處理团秽,阻止了事件的傳遞,對于這種特殊的問題叭首,只能做一下單獨的處理习勤,在它處理之前,判斷它的子view中是否有我們需要的view焙格,如果需要就獲取view做相應(yīng)的處理图毕。
還有一個問題就是dialog中的view是無法處理的,因為在Android中眷唉,dialog其實與activity都有自己的wondow予颤,獨立與activity的view層級囤官,它與activity有各自的點擊事件傳遞,所以要針對dialog的事件傳遞做單獨的處理蛤虐,在baseDialog的dispatchTouchEvent中做與activity相同的判斷党饮,根據(jù)模式的不同做不同的處理。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
..
..
/**
* 可視化埋點 在配置模式下就返回false驳庭,非配置模式下判斷點是否在上報map中刑顺,有就上報
*/
VisualAnalysisManager visualAnalysisManager = VisualAnalysisManager.getInstance();
if(ev.getAction() == MotionEvent.ACTION_UP)
if (visualAnalysisManager.getIsInConfigVisual()) {
//配置模式
visualAnalysisManager.handleTouchEventInConfigMode(ev);
return false;
}else {
//用戶模式
//截取up事件,讓down事件傳遞下去,來獲取當(dāng)前點擊的view
visualAnalysisManager.handleTouchEventInNormalMode(ev);
}
..
..
復(fù)制代碼
五饲常、待優(yōu)化的viewID問題
viewID是根據(jù)view的層級來確定的蹲堂,如果項目進(jìn)行重構(gòu)或者變更層級,相同view的viewID就會變化贝淤,導(dǎo)致很多view要重新配置柒竞,有一種約束ID的方案,就是單獨生成一個view與viewID的對應(yīng)表播聪,但是這樣在添加新view時又要做相應(yīng)的對應(yīng)朽基,也會帶來開發(fā)上的不便利,所以目前還是維持現(xiàn)有的生成viewID的方案离陶,當(dāng)遇到重構(gòu)或者層級變動的問題時就只能將上報的點遷移并重新生成viewID再上報稼虎,這是一個待優(yōu)化的方向,后續(xù)想到合適的方案時會將其優(yōu)化枕磁。