(一)源码调试:设置build中编译版本为23(6.0);必须使用google官方6.0系统手机(如Nexus系列且安装6.0系统)或者使用虚拟机(配置也是Nexus6.0系统)。这样要求是为了运行设备和编译器编译使用同一个版本,而且都是google官方版本,在调试打断点的时候就不会出现断点行号和源码行号对不上的问题了。切记调试条件:
1、编译版本和测试机版本必须相同。
2、测试机使用Google官方版本,Nexus系列或者虚拟机。(使用其他品牌真机调试,因为手机框架层是被定制修改过的,所以会和编译器上的源码对不上)
(二)源码使用的是6.0系统源码。
(三)
代码结构:A继承自RelativeLayout;B继承自RelativeLayout;C继承自TextView;都重写dispatchTouchEvent和onTouchEvent两个方法并打入Log;
测试代码段:ViewGroup类dispatchTouchEvent方法。
断点代码行数:ViewGroup类,断点1:2197行,断点2:2238行。
注意:以下的验证和猜想都是针对Down动作的,不涉及move和up等。
已验证过程:假设A包含B,B包含C,所有dispatchTouchEvent和onTouchEvent返回默认值,点击C,会先调用A的dispatchTouchEvent,在断点1处停留,断点1处调用方法dispatchTransformedTouchEvent,它的参数child就是B,这个方法会使B调用自己的dispatchTouchEvent;又在断点1处停留,调用dispatchTransformedTouchEvent,它的参数child是C,方法中C会调用自己的dispatchTouchEvent;C是一个View,View的dispatchTouchEvent方法会调用自己的onTouchEvent并返回false。注意,到此,A》B》C的dispatchTouchEvent调用就完毕了,并且C执行了返回,后面就是C》B》A的onTouchEvent返回过程了,这就是个递归。继续断点,B在断点1处得到C的返回值,能够继续执行了,并且在B的断点2处停留,断点2处会再次调用B的dispatchTransformedTouchEvent方法,并且参数child为null,方法就会调用super.dispatchTouchEvent(也就是调用B的父类View的dispatchTouchEvent),然后就会调用B的onTouchEvent方法并返回false。这样一来A在断点1处有了返回值就可以继续执行了,并且在A的端点2处停留,端点2处会再次调用A的dispatchTransformedTouchEvent方法,并且参数child为null,方法会调用super.dispatchTouchEvent,方法中就调用A的onTouchEvent。至此,就完成了C》B》A的onTouchEvent回溯过程了。做一个形象的比喻:Android的触碰过程就像走楼梯,由十层一层一层的走到一层,在一层走到大厦另一边的楼梯,再由一层一层一层走到十层。默认情况下你是不可以直接由7层走到另一侧的楼梯的。
猜想一:在B的dispatchTouchEvent直接返回true,则A在断点1处停留,调用dispatchTransformedTouchEvent方法,参数child是B,B调用自己的dispatchTouchEvent并直接返回true。则A在断点1处有了返回true可以继续执行,然后就在A的断点2处停留,最后会调用到A的onTouchEvent
猜想一结果:真实的运行结果是执行了A的dispatchTouchEvent和B的dispatchTouchEvent就结束了,并没有执行A的onTouchEvent。
猜想一分析:猜想和结果的出入就是最后是否调用了A的onTouchEvent,看断点1处的代码:
A执行dispatchTransformedTouchEvent,也就是想B分发,执行B的dispatchTouchEvent,根据上面的猜想B的dispatchTouchEvent会直接返回true,if成立,然后就会执行2213和2214行,2214行给标志位alreadyDispatchedToNewTouchTarget置为true,2213行执行addTouchTarget方法并把返回值置给newTouchTarget:
在addTouchTarget方法中给mFirstTouchTarget赋值并把一个相同值返回付给了newTouchTarget。
再回到流程中看,B的dispatchTouchEvent直接返回true,A的dispatchTransformedTouchEvent有了返回值就可以继续执行代码,执行到断点2处又碰到一个if判断:
由断点1处分析可知,mFirstTouchTarget等于newTouchTarget且不等于null,所有if不成立(跳过了回溯过程中调用dispatchTransformedTouchEvent的第一次机会),执行2243-2270行,其中2249行又遇到一个if判断,判断条件alreadyDispatchedToNewTouchTarget等于true、target也的确等于newTouchTarget(见前面),if成立(跳过了回溯过程中调用dispatchTransformedTouchEvent的第二次机会)。在dispatchTouchEvent直接返回true而导致的后续过程中我们可以看到,我们根本没有机会运行到dispatchTransformedTouchEvent方法,也就没有机会执行到onTouchEvent方法。
猜想二:在B的onTouchEvent直接返回true,则完整执行dispatchTouchEvent的A》B》C过程,onTouchEvent方法的回溯过程只执行到B就结束。
猜想二结果:点击C,执行过程:A的dispatchTouchEvent》B的dispatchTouchEvent》C的dispatchTouchEvent》C的onTouchEvent》B的onTouchEvent结束。与猜想一致。
下面是对UP事件的分析:
在猜想二验证结果时,程序的log除了显示上面的“猜想二结果”,还显示了一套UP事件的传递log:A的dispatchTouchEvent》B的dispatchTouchEvent》B的onTouchEvent结束。
也就是说目标是要从三楼左侧的楼梯去到二楼右侧的楼梯。D君(Down事件)先从三楼左侧楼梯一直下到一楼,穿过一楼楼层到达一楼右侧楼梯,在爬到二楼右侧楼梯,到达!U君(UP事件)从三楼左侧楼梯下到二楼,然后直接穿过二楼楼层到达二楼右侧楼梯,到达!!
源码上说up流程的成因只需要明白一点:down流程之所以会有dispatchTouchEvent下发和onTouchEvent回溯两个对称流程,是因为代码层次上有断点1和断点2两个地方能够执行两次dispatch方法(第一次目的是为了能够调用dispatchTouchEvent,第二次目的是为了能够调用onTouchEvent)。而对于up流程,在源码中断点1是包含在一个if语句中(2143-2145):
if(actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)
可以看到,只有down和move事件能够执行断点1处的dispatch,所以up就只能执行一次断点2处的dispatch了,紧接着我们再来看一遍断点2代码段:
仔细看,无论if是否成立,都会调用dispatchTransformedTouchEvent方法,不同的是方法的第三个参数child不同,下面直接给出结论:当mFirstTouchTarget为null是dispatchTransformedTouchEvent的child传null,则调用当前类的onTouchEvent方法;反之,child传子View,则调用子View的dispatchTouchEvent方法。
这个结论的关键点是mFirstTouchTarget是否为null,当它不为null时dispatchTouchEvent继续下放,当它为null时调用同级的onTouchEvent并开始回溯。
那mFirstTouchTarget是在哪里设置的呢?mFirstTouchTarget只有在down过程中才会被设置,具体参考猜想一中的分析。我们可以想象得到,在down过程中,当B的onTouchEvent返回true,调用它的A中mFirstTouchTarget就会被赋值(A的父集们的mFirstTouchTarget都会递归被赋值),而B中的mFirstTouchTarget还是保持为null。有了这个结果,在up过程中到了B的断点2处,mFirstTouchTarget为null,就会调用同级的onTouchEvent,这样就实现了从三楼左侧楼梯下到二楼直接穿过楼层到达二楼右侧楼梯。
没有实践调试的过程,看起来绝对是似是而非似懂非懂的,所以Debug源码才是最终理解的正途!!!
网友评论