(一)源碼調(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è)備和編譯器編譯使用同一個(gè)版本,而且都是google官方版本,在調(diào)試打斷點(diǎn)的時(shí)候就不會(huì)出現(xiàn)斷點(diǎn)行號(hào)和源碼行號(hào)對(duì)不上的問題了。切記調(diào)試條件:
1、編譯版本和測(cè)試機(jī)版本必須相同。
2、測(cè)試機(jī)使用Google官方版本,Nexus系列或者虛擬機(jī)。(使用其他品牌真機(jī)調(diào)試,因?yàn)槭謾C(jī)框架層是被定制修改過的,所以會(huì)和編譯器上的源碼對(duì)不上)
(二)源碼使用的是6.0系統(tǒng)源碼。
(三)
代碼結(jié)構(gòu):A繼承自RelativeLayout;B繼承自RelativeLayout;C繼承自TextView;都重寫dispatchTouchEvent和onTouchEvent兩個(gè)方法并打入Log;
測(cè)試代碼段:ViewGroup類dispatchTouchEvent方法。
斷點(diǎn)代碼行數(shù):ViewGroup類,斷點(diǎn)1:2197行,斷點(diǎn)2:2238行。
注意:以下的驗(yàn)證和猜想都是針對(duì)Down動(dòng)作的,不涉及move和up等。
已驗(yàn)證過程:假設(shè)A包含B,B包含C,所有dispatchTouchEvent和onTouchEvent返回默認(rèn)值,點(diǎn)擊C,會(huì)先調(diào)用A的dispatchTouchEvent,在斷點(diǎn)1處停留,斷點(diǎn)1處調(diào)用方法dispatchTransformedTouchEvent,它的參數(shù)child就是B,這個(gè)方法會(huì)使B調(diào)用自己的dispatchTouchEvent;又在斷點(diǎn)1處停留,調(diào)用dispatchTransformedTouchEvent,它的參數(shù)child是C,方法中C會(huì)調(diào)用自己的dispatchTouchEvent;C是一個(gè)View,View的dispatchTouchEvent方法會(huì)調(diào)用自己的onTouchEvent并返回false。注意,到此,A》B》C的dispatchTouchEvent調(diào)用就完畢了,并且C執(zhí)行了返回,后面就是C》B》A的onTouchEvent返回過程了,這就是個(gè)遞歸。繼續(xù)斷點(diǎn),B在斷點(diǎn)1處得到C的返回值,能夠繼續(xù)執(zhí)行了,并且在B的斷點(diǎn)2處停留,斷點(diǎn)2處會(huì)再次調(diào)用B的dispatchTransformedTouchEvent方法,并且參數(shù)child為null,方法就會(huì)調(diào)用super.dispatchTouchEvent(也就是調(diào)用B的父類View的dispatchTouchEvent),然后就會(huì)調(diào)用B的onTouchEvent方法并返回false。這樣一來A在斷點(diǎn)1處有了返回值就可以繼續(xù)執(zhí)行了,并且在A的端點(diǎn)2處停留,端點(diǎn)2處會(huì)再次調(diào)用A的dispatchTransformedTouchEvent方法,并且參數(shù)child為null,方法會(huì)調(diào)用super.dispatchTouchEvent,方法中就調(diào)用A的onTouchEvent。至此,就完成了C》B》A的onTouchEvent回溯過程了。做一個(gè)形象的比喻: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處停留,最后會(huì)調(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會(huì)直接返回true,if成立,然后就會(huì)執(zhí)行2213和2214行,2214行給標(biāo)志位alreadyDispatchedToNewTouchTarget置為true,2213行執(zhí)行addTouchTarget方法并把返回值置給newTouchTarget:

在addTouchTarget方法中給mFirstTouchTarget賦值并把一個(gè)相同值返回付給了newTouchTarget。
再回到流程中看,B的dispatchTouchEvent直接返回true,A的dispatchTransformedTouchEvent有了返回值就可以繼續(xù)執(zhí)行代碼,執(zhí)行到斷點(diǎn)2處又碰到一個(gè)if判斷:

由斷點(diǎn)1處分析可知,mFirstTouchTarget等于newTouchTarget且不等于null,所有if不成立(跳過了回溯過程中調(diào)用dispatchTransformedTouchEvent的第一次機(jī)會(huì)),執(zhí)行2243-2270行,其中2249行又遇到一個(gè)if判斷,判斷條件alreadyDispatchedToNewTouchTarget等于true、target也的確等于newTouchTarget(見前面),if成立(跳過了回溯過程中調(diào)用dispatchTransformedTouchEvent的第二次機(jī)會(huì))。在dispatchTouchEvent直接返回true而導(dǎo)致的后續(xù)過程中我們可以看到,我們根本沒有機(jī)會(huì)運(yùn)行到dispatchTransformedTouchEvent方法,也就沒有機(jī)會(huì)執(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é)束。與猜想一致。
下面是對(duì)UP事件的分析:
在猜想二驗(yàn)證結(jié)果時(shí),程序的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á)?。?/b>
源碼上說up流程的成因只需要明白一點(diǎn):down流程之所以會(huì)有dispatchTouchEvent下發(fā)和onTouchEvent回溯兩個(gè)對(duì)稱流程,是因?yàn)榇a層次上有斷點(diǎn)1和斷點(diǎn)2兩個(gè)地方能夠執(zhí)行兩次dispatch方法(第一次目的是為了能夠調(diào)用dispatchTouchEvent,第二次目的是為了能夠調(diào)用onTouchEvent)。而對(duì)于up流程,在源碼中斷點(diǎn)1是包含在一個(gè)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,所以u(píng)p就只能執(zhí)行一次斷點(diǎn)2處的dispatch了,緊接著我們?cè)賮砜匆槐閿帱c(diǎn)2代碼段:

仔細(xì)看,無論if是否成立,都會(huì)調(diào)用dispatchTransformedTouchEvent方法,不同的是方法的第三個(gè)參數(shù)child不同,下面直接給出結(jié)論:當(dāng)mFirstTouchTarget為null是dispatchTransformedTouchEvent的child傳null,則調(diào)用當(dāng)前類的onTouchEvent方法;反之,child傳子View,則調(diào)用子View的dispatchTouchEvent方法。
這個(gè)結(jié)論的關(guān)鍵點(diǎn)是mFirstTouchTarget是否為null,當(dāng)它不為null時(shí)dispatchTouchEvent繼續(xù)下放,當(dāng)它為null時(shí)調(diào)用同級(jí)的onTouchEvent并開始回溯。
那mFirstTouchTarget是在哪里設(shè)置的呢?mFirstTouchTarget只有在down過程中才會(huì)被設(shè)置,具體參考猜想一中的分析。我們可以想象得到,在down過程中,當(dāng)B的onTouchEvent返回true,調(diào)用它的A中mFirstTouchTarget就會(huì)被賦值(A的父集們的mFirstTouchTarget都會(huì)遞歸被賦值),而B中的mFirstTouchTarget還是保持為null。有了這個(gè)結(jié)果,在up過程中到了B的斷點(diǎn)2處,mFirstTouchTarget為null,就會(huì)調(diào)用同級(jí)的onTouchEvent,這樣就實(shí)現(xiàn)了從三樓左側(cè)樓梯下到二樓直接穿過樓層到達(dá)二樓右側(cè)樓梯。
沒有實(shí)踐調(diào)試的過程,看起來絕對(duì)是似是而非似懂非懂的,所以Debug源碼才是最終理解的正途?。?!