(一)源碼調(diào)試:設(shè)置build中編譯版本為23(6.0);必須使用google官方6.0系統(tǒng)手機(jī)(如Nexus系列且安裝6.0系統(tǒng))或者使用虛擬機(jī)(配置也是Nexus6.0系統(tǒng))。這樣要求是為了運(yùn)行設(shè)備和編譯器編譯使用同一個版本安疗,而且都是google官方版本抄邀,在調(diào)試打斷點(diǎn)的時候就不會出現(xiàn)斷點(diǎn)行號和源碼行號對不上的問題了掠兄。切記調(diào)試條件:
1喂击、編譯版本和測試機(jī)版本必須相同剂癌。
2、測試機(jī)使用Google官方版本惭等,Nexus系列或者虛擬機(jī)珍手。(使用其他品牌真機(jī)調(diào)試办铡,因?yàn)槭謾C(jī)框架層是被定制修改過的辞做,所以會和編譯器上的源碼對不上)
(二)源碼使用的是6.0系統(tǒng)源碼。
(三)
代碼結(jié)構(gòu):A繼承自RelativeLayout寡具;B繼承自RelativeLayout秤茅;C繼承自TextView;都重寫dispatchTouchEvent和onTouchEvent兩個方法并打入Log童叠;
測試代碼段:ViewGroup類dispatchTouchEvent方法框喳。
斷點(diǎn)代碼行數(shù):ViewGroup類课幕,斷點(diǎn)1:2197行,斷點(diǎn)2:2238行五垮。
注意:以下的驗(yàn)證和猜想都是針對Down動作的乍惊,不涉及move和up等。
已驗(yàn)證過程:假設(shè)A包含B放仗,B包含C润绎,所有dispatchTouchEvent和onTouchEvent返回默認(rèn)值,點(diǎn)擊C诞挨,會先調(diào)用A的dispatchTouchEvent莉撇,在斷點(diǎn)1處停留,斷點(diǎn)1處調(diào)用方法dispatchTransformedTouchEvent惶傻,它的參數(shù)child就是B棍郎,這個方法會使B調(diào)用自己的dispatchTouchEvent;又在斷點(diǎn)1處停留银室,調(diào)用dispatchTransformedTouchEvent,它的參數(shù)child是C涂佃,方法中C會調(diào)用自己的dispatchTouchEvent;C是一個View蜈敢,View的dispatchTouchEvent方法會調(diào)用自己的onTouchEvent并返回false巡李。注意,到此扶认,A》B》C的dispatchTouchEvent調(diào)用就完畢了侨拦,并且C執(zhí)行了返回,后面就是C》B》A的onTouchEvent返回過程了辐宾,這就是個遞歸狱从。繼續(xù)斷點(diǎn),B在斷點(diǎn)1處得到C的返回值叠纹,能夠繼續(xù)執(zhí)行了季研,并且在B的斷點(diǎn)2處停留,斷點(diǎn)2處會再次調(diào)用B的dispatchTransformedTouchEvent方法誉察,并且參數(shù)child為null与涡,方法就會調(diào)用super.dispatchTouchEvent(也就是調(diào)用B的父類View的dispatchTouchEvent),然后就會調(diào)用B的onTouchEvent方法并返回false持偏。這樣一來A在斷點(diǎn)1處有了返回值就可以繼續(xù)執(zhí)行了驼卖,并且在A的端點(diǎn)2處停留,端點(diǎn)2處會再次調(diào)用A的dispatchTransformedTouchEvent方法鸿秆,并且參數(shù)child為null酌畜,方法會調(diào)用super.dispatchTouchEvent,方法中就調(diào)用A的onTouchEvent卿叽。至此桥胞,就完成了C》B》A的onTouchEvent回溯過程了恳守。做一個形象的比喻:Android的觸碰過程就像走樓梯,由十層一層一層的走到一層贩虾,在一層走到大廈另一邊的樓梯催烘,再由一層一層一層走到十層。默認(rèn)情況下你是不可以直接由7層走到另一側(cè)的樓梯的缎罢。
猜想一:在B的dispatchTouchEvent直接返回true颗圣,則A在斷點(diǎn)1處停留,調(diào)用dispatchTransformedTouchEvent方法屁使,參數(shù)child是B在岂,B調(diào)用自己的dispatchTouchEvent并直接返回true。則A在斷點(diǎn)1處有了返回true可以繼續(xù)執(zhí)行蛮寂,然后就在A的斷點(diǎn)2處停留蔽午,最后會調(diào)用到A的onTouchEvent
猜想一結(jié)果:真實(shí)的運(yùn)行結(jié)果是執(zhí)行了A的dispatchTouchEvent和B的dispatchTouchEvent就結(jié)束了,并沒有執(zhí)行A的onTouchEvent酬蹋。
猜想一分析:猜想和結(jié)果的出入就是最后是否調(diào)用了A的onTouchEvent及老,看斷點(diǎn)1處的代碼:
A執(zhí)行dispatchTransformedTouchEvent,也就是想B分發(fā)范抓,執(zhí)行B的dispatchTouchEvent骄恶,根據(jù)上面的猜想B的dispatchTouchEvent會直接返回true,if成立匕垫,然后就會執(zhí)行2213和2214行僧鲁,2214行給標(biāo)志位alreadyDispatchedToNewTouchTarget置為true,2213行執(zhí)行addTouchTarget方法并把返回值置給newTouchTarget:
在addTouchTarget方法中給mFirstTouchTarget賦值并把一個相同值返回付給了newTouchTarget象泵。
再回到流程中看寞秃,B的dispatchTouchEvent直接返回true,A的dispatchTransformedTouchEvent有了返回值就可以繼續(xù)執(zhí)行代碼偶惠,執(zhí)行到斷點(diǎn)2處又碰到一個if判斷:
由斷點(diǎn)1處分析可知春寿,mFirstTouchTarget等于newTouchTarget且不等于null,所有if不成立(跳過了回溯過程中調(diào)用dispatchTransformedTouchEvent的第一次機(jī)會)忽孽,執(zhí)行2243-2270行绑改,其中2249行又遇到一個if判斷,判斷條件alreadyDispatchedToNewTouchTarget等于true兄一、target也的確等于newTouchTarget(見前面)厘线,if成立(跳過了回溯過程中調(diào)用dispatchTransformedTouchEvent的第二次機(jī)會)。在dispatchTouchEvent直接返回true而導(dǎo)致的后續(xù)過程中我們可以看到瘾腰,我們根本沒有機(jī)會運(yùn)行到dispatchTransformedTouchEvent方法皆的,也就沒有機(jī)會執(zhí)行到onTouchEvent方法覆履。
猜想二:在B的onTouchEvent直接返回true蹋盆,則完整執(zhí)行dispatchTouchEvent的A》B》C過程费薄,onTouchEvent方法的回溯過程只執(zhí)行到B就結(jié)束。
猜想二結(jié)果:點(diǎn)擊C栖雾,執(zhí)行過程:A的dispatchTouchEvent》B的dispatchTouchEvent》C的dispatchTouchEvent》C的onTouchEvent》B的onTouchEvent結(jié)束楞抡。與猜想一致。
下面是對UP事件的分析:
在猜想二驗(yàn)證結(jié)果時析藕,程序的log除了顯示上面的“猜想二結(jié)果”召廷,還顯示了一套UP事件的傳遞log:A的dispatchTouchEvent》B的dispatchTouchEvent》B的onTouchEvent結(jié)束。
也就是說目標(biāo)是要從三樓左側(cè)的樓梯去到二樓右側(cè)的樓梯账胧。D君(Down事件)先從三樓左側(cè)樓梯一直下到一樓竞慢,穿過一樓樓層到達(dá)一樓右側(cè)樓梯,在爬到二樓右側(cè)樓梯治泥,到達(dá)筹煮!U君(UP事件)從三樓左側(cè)樓梯下到二樓,然后直接穿過二樓樓層到達(dá)二樓右側(cè)樓梯居夹,到達(dá)0芰省!
源碼上說up流程的成因只需要明白一點(diǎn):down流程之所以會有dispatchTouchEvent下發(fā)和onTouchEvent回溯兩個對稱流程准脂,是因?yàn)榇a層次上有斷點(diǎn)1和斷點(diǎn)2兩個地方能夠執(zhí)行兩次dispatch方法(第一次目的是為了能夠調(diào)用dispatchTouchEvent劫扒,第二次目的是為了能夠調(diào)用onTouchEvent)。而對于up流程狸膏,在源碼中斷點(diǎn)1是包含在一個if語句中(2143-2145):
if(actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)
可以看到沟饥,只有down和move事件能夠執(zhí)行斷點(diǎn)1處的dispatch,所以up就只能執(zhí)行一次斷點(diǎn)2處的dispatch了,緊接著我們再來看一遍斷點(diǎn)2代碼段:
仔細(xì)看湾戳,無論if是否成立闷板,都會調(diào)用dispatchTransformedTouchEvent方法,不同的是方法的第三個參數(shù)child不同院塞,下面直接給出結(jié)論:當(dāng)mFirstTouchTarget為null是dispatchTransformedTouchEvent的child傳null遮晚,則調(diào)用當(dāng)前類的onTouchEvent方法;反之拦止,child傳子View县遣,則調(diào)用子View的dispatchTouchEvent方法。
這個結(jié)論的關(guān)鍵點(diǎn)是mFirstTouchTarget是否為null汹族,當(dāng)它不為null時dispatchTouchEvent繼續(xù)下放萧求,當(dāng)它為null時調(diào)用同級的onTouchEvent并開始回溯。
那mFirstTouchTarget是在哪里設(shè)置的呢顶瞒?mFirstTouchTarget只有在down過程中才會被設(shè)置夸政,具體參考猜想一中的分析。我們可以想象得到榴徐,在down過程中守问,當(dāng)B的onTouchEvent返回true匀归,調(diào)用它的A中mFirstTouchTarget就會被賦值(A的父集們的mFirstTouchTarget都會遞歸被賦值),而B中的mFirstTouchTarget還是保持為null耗帕。有了這個結(jié)果穆端,在up過程中到了B的斷點(diǎn)2處,mFirstTouchTarget為null仿便,就會調(diào)用同級的onTouchEvent体啰,這樣就實(shí)現(xiàn)了從三樓左側(cè)樓梯下到二樓直接穿過樓層到達(dá)二樓右側(cè)樓梯。
沒有實(shí)踐調(diào)試的過程嗽仪,看起來絕對是似是而非似懂非懂的荒勇,所以Debug源碼才是最終理解的正途!N偶帷枕屉!