2018年11月25日星期日

当我们输入一条 SQL 查询语句时,发生了什么?

当我们输入一条 SQL 查询语句时,发生了什么? 

当我们输入一条 SQL 查询语句时,发生了什么?

2018 年 11 月 23 日
我们经常说,看一个事儿千万不要直接陷入细节里,你应该先鸟瞰其全貌,这样能够帮助你从高维度理解问题。同样,对于 MySQL 的学习也是这样。平时我们使用数据库,看到的通常都是一个整体。比如,你有个最简单的表,表里只有一个 ID 字段,在执行下面这个查询语句时:
复制代码
mysql> select * from T where ID=10
我们看到的只是输入一条语句,返回一个结果,却不知道这条语句在 MySQL 内部的执行过程。
所以今天我想和你一起把 MySQL 拆解一下,看看里面都有哪些“零件”,希望借由这个拆解过程,让你对 MySQL 有更深入的理解。这样当我们碰到 MySQL 的一些异常或者问题时,就能够直戳本质,更为快速地定位并解决问题。
下面我给出的是 MySQL 的基本架构示意图,从中你可以清楚地看到 SQL 语句在 MySQL 的各个功能模块中的执行过程。
MySQL 的逻辑架构图
大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。
Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。
也就是说,你执行 create table 建表的时候,如果不指定引擎类型,默认使用的就是 InnoDB。不过,你也可以通过指定存储引擎的类型来选择别的引擎,比如在 create table 语句中使用 engine=memory, 来指定使用内存引擎创建表。不同存储引擎的表数据存取方式不同,支持的功能也不同,在后面的文章中,我们会讨论到引擎的选择。
从图中不难看出,不同的存储引擎共用一个 Server 层,也就是从连接器到执行器的部分。你可以先对每个组件的名字有个印象,接下来我会结合开头提到的那条 SQL 语句,带你走一遍整个执行流程,依次看下每个组件的作用。

连接器

第一步,你会先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般是这么写的:
复制代码
mysql -h$ip -P$port -u$user -p
输完命令之后,你就需要在交互对话里面输入密码。虽然密码也可以直接跟在 -p 后面写在命令行中,但这样可能会导致你的密码泄露。如果你连的是生产服务器,强烈建议你不要这么做。
连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证你的身份,这个时候用的就是你输入的用户名和密码。
  • 如果用户名或密码不对,你就会收到一个 "Access denied for user" 的错误,然后客户端程序结束执行。
  • 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。
这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。
连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 show processlist 命令中看到它。文本中这个图是 show processlist 的结果,其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接。
客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。
如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。
数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。
建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。
但是全部使用长连接后,你可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。
怎么解决这个问题呢?你可以考虑以下两种方案。
  1. 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
  2. 如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

查询缓存

连接建立完成后,你就可以执行 select 语句了。执行逻辑就会来到第二步:查询缓存。
MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。
如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。
但是大多数情况下我会建议你不要使用查询缓存,为什么呢?因为查询缓存往往弊大于利
查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。
好在 MySQL 也提供了这种“按需使用”的方式。你可以将参数 query_cache_type 设置成 DEMAND,这样对于默认的 SQL 语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可以用 SQL_CACHE 显式指定,像下面这个语句一样:
复制代码
mysql> select SQL_CACHE * from T where ID=10
需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。

分析器

如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。
分析器先会做“词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。
MySQL 从你输入的 "select" 这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。
做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。
如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 select 少打了开头的字母“s”。
复制代码
mysql> elect * from t where ID=1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'elect * from t where ID=1' at line 1
一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接“use near”的内容。

优化器

经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。比如你执行下面这样的语句,这个语句是执行两个表的 join:
复制代码
mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
  • 既可以先从表 t1 里面取出 c=10 的记录的 ID 值,再根据 ID 值关联到表 t2,再判断 t2 里面 d 的值是否等于 20。
  • 也可以先从表 t2 里面取出 d=20 的记录的 ID 值,再根据 ID 值关联到 t1,再判断 t1 里面 c 的值是否等于 10。
这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。
优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。如果你还有一些疑问,比如优化器是怎么选择索引的,有没有可能选择错等等,没关系,我会在后面的文章中单独展开说明优化器的内容。

执行器

MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。
开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示。
复制代码
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:
  1. 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
  2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
  3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
至此,这个语句就执行完成了。
对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。
你会在数据库的慢查询日志中看到一个 rows_examined 的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。
在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟 rows_examined 并不是完全相同的

2015年7月12日星期日

Android 消息机制及消息分发详细

android的消息处理机制

Looper
Handler
Message
参考http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html
android的消息处理有三个核心类:Looper,Handler和Message。其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因此我没将其作为核心类。下面一一介绍:

线程的魔法师 Looper

Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:
View Code
通过上面两行核心代码,你的线程就升级为Looper线程了!!!是不是很神奇?让我们放慢镜头,看看这两行代码各自做了什么。
1)Looper.prepare()
通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,为什么呢?咱们来看源码。
View Code
通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。如果你还不清楚什么是ThreadLocal,请参考《理解ThreadLocal》
2)Looper.loop()
调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。其源码分析如下:
View Code
除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如
Looper.myLooper()得到当前线程looper对象:
View Code
getThread()得到looper对象所属线程:
View Code
quit()方法结束looper循环:
View Code
到此为止,你应该对Looper有了基本的了解,总结几点:
1.每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal
2.Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行
3.Looper使一个线程变成Looper线程。
那么,我们如何往MQ上添加消息呢?下面有请Handler!(掌声~~~)

异步处理大师 Handler

什么是handler?handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。默认的构造方法:
View Code
下面我们就可以为之前的LooperThread类加入Handler:
View Code
加入handler后的效果如下图:
可以看到,一个线程可以有多个Handler,但是只能有一个Looper!
Handler发送消息
有了handler之后,我们就可以使用 post(Runnable)postAtTime(Runnable, long)postDelayed(Runnable, long)sendEmptyMessage(int),sendMessage(Message)sendMessageAtTime(Message, long)和 sendMessageDelayed(Message, long)这些方法向MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了,见源码:
View Code
其他方法就不罗列了,总之通过handler发出的message有如下特点:
1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码
msg.target.dispatchMessage(msg);
2.post发出的message,其callback为Runnable对象
Handler处理消息
说完了消息的发送,再来看下handler如何处理消息。消息的处理是通过核心方法dispatchMessage(Message msg)与钩子方法handleMessage(Message msg)完成的,见源码
View Code
可以看到,除了handleMessage(Message msg)和Runnable对象的run方法由开发者实现外(实现具体逻辑),handler的内部工作机制对开发者是透明的。这正是handler API设计的精妙之处!
Handler的用处
我在小标题中将handler描述为“异步处理大师”,这归功于Handler拥有下面两个重要的特点:
1.handler可以在任意线程发送消息,这些消息会被添加到关联的MQ上。
              
2.handler是在它关联的looper线程中处理消息的。
这就解决了android最经典的不能在其他非主线程中更新UI的问题。android的主线程也是一个looper线程(looper在android中运用很广),我们在其中创建的handler默认将关联主线程MQ。因此,利用handler的一个solution就是在activity中创建handler并将其引用传递给worker thread,worker thread执行完任务后使用handler发送消息通知activity更新UI。(过程如图)
下面给出sample代码,仅供参考:
View Code
View Code
当然,handler能做的远远不仅如此,由于它能post Runnable对象,它还能与Looper配合实现经典的Pipeline Thread(流水线线程)模式。请参考此文《Android Guts: Intro to Loopers and Handlers》

封装任务 Message

在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,这里不做总结了。但是有这么几点需要注意(待补充):
1.尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。
2.如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
3.擅用message.what来标识信息,以便用不同方式处理message。




消息分发:
http://stackvoid.com/details-dispatch-onTouch-Event-in-Android/

Android 事件分发机制详解

网上很多关于Android事件分发机制的解释,大多数描述的都不够清晰,没有吧来龙去脉搞清楚,本文将带你从Touch事件产生到Touch事件被消费这一全过程作全面的剖析。

产生Touch事件

这部分牵扯到硬件和Linux内核部分;我们简单讲述一下这部分内容,如果有兴趣的话可以参考这篇文章

传递Touch事件

触摸事件是由Linux内核的一个Input子系统来管理的(InputManager),Linux子系统会在/dev/input/ 这个路径下创建硬件输入设备节点(这里的硬件设备就是我们的触摸屏了)。当手指触动触摸屏时,硬件设备通过设备节点像内核(其实是InputManager管理)报告事件,InputManager 经过处理将此事件传给 Android系统的一个系统Service:WindowManagerService 。
TouchEvent01
WindowManagerService调用dispatchPointer()从存放WindowState的z-order顺序列表中找到能接收当前touch事件的 WindowState,通过IWindow代理将此消息发送到IWindow服务端(IWindow.Stub子类),这个IWindow.Stub属于ViewRoot(这个类继承Handler,主要用于连接PhoneWindow和WindowManagerService),所以事件就传到了ViewRoot.dispatchPointer()中.
我们来看一下ViewRoot的dispatchPointer方法:
1 public void dispatchPointer(MotionEvent event, long eventTime,
2             boolean callWhenDone) {
3         Message msg = obtainMessage(DISPATCH_POINTER);
4         msg.obj = event;
5         msg.arg1 = callWhenDone ? 1 : 0;
6         sendMessageAtTime(msg, eventTime);
7     }
dispatchPointer方法就是把这个事件封装成Message发送出去,在ViewRoot Handler的handleMessage中被处理,其调用了mView.dispatchTouchEvent方法(mView是一个PhoneWindow.DecorView对象),PhoneWindow.DecorView继承FrameLayout(FrameLayout继承ViewGroup,ViewGroup继承自View),DecorView里的dispatchTouchEvent方法如下. 这里的Callback的cb其实就是Activity的attach()方法里的设置回调。
 1 //in file PhoneWindow.java
 2 public boolean dispatchTouchEvent(MotionEvent ev) {
 3             final Callback cb = getCallback();
 4             if (mEnableFaceDetection) {
 5                 int pointCount = ev.getPointerCount();
 6 
 7                 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
 8                 case MotionEvent.ACTION_POINTER_DOWN:
 9          
10          .......
11 
12             return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
13                     : super.dispatchTouchEvent(ev);
14         }
15 //in file Activity.java -> attach()
16         mFragments.attachActivity(this, mContainer, null);
17         mWindow = PolicyManager.makeNewWindow(this);
18         mWindow.setCallback(this);//设置回调
也就是说,正常情形下,当前的Activity就是这里的cb,即调用了Activity的dispatchTouchEvent方法。
下面来分析一下从Activity到各个子View的事件传递和处理过程。
首先先分析Activity的dispatchTouchEvent方法。
1 public boolean dispatchTouchEvent(MotionEvent ev) {
2         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3             onUserInteraction();
4         }
5         if (getWindow().superDispatchTouchEvent(ev)) {
6             return true;
7         }
8         return onTouchEvent(ev);
9     }
onUserInteraction() 是一个空方法,开发者可以根据自己的需求覆写这个方法(这个方法在一个Touch事件的周期肯定会调用到的)。如果判断成立返回True,当前事件就不在传播下去了。 superDispatchTouchEvent(ev) 这个方法做了什么呢? getWindow().superDispatchTouchEvent(ev) 也就是调用了PhoneWindow.superDispatchTouchEvent 方法,而这个方法返回的是 mDecor.superDispatchTouchEvent(event),在内部类 DecorView(上文中的mDecor) 的superDispatchTouchEvent 中调用super.dispatchTouchEvent(event),而DecorView继承自ViewGroup(通过FrameLayout,FrameLayout没有dispatchTouchEvent),最终调用的是ViewGroup的dispatchTouchEvent方法。
小结一下。Event事件是首先到了 PhoneWindow 的 DecorView 的 dispatchTouchEvent 方法,此方法通过 CallBack 调用了 Activity 的 dispatchTouchEvent 方法,在 Activity 这里,我们可以重写 Activity 的dispatchTouchEvent 方法阻断 touch事件的传播。接着在Activity里的dispatchTouchEvent 方法里,事件又再次传递到DecorView,DecorView通过调用父类(ViewGroup)的dispatchTouchEvent 将事件传给父类处理,也就是我们下面要分析的方法,这才进入网上大部分文章讲解的touch事件传递流程。
为什么要从 PhoneWindow.DecorView 中 传到 Activity,然后在传回 PhoneWindow.DecorView 中呢? 主要是为了方便在Activity中通过控制dispatchTouchEvent 来控制当前Activity 事件的分发, 下一篇关于数据埋点文章就应用了这个机制
OK,我们要重点分析的就是ViewGroup中的dispatchTouchEvent方法。
  1 @Override
  2     public boolean dispatchTouchEvent(MotionEvent ev) {
  3             //......
  4 
  5             // Check for interception.
  6             final boolean intercepted;//是否被拦截
  7             if (actionMasked == MotionEvent.ACTION_DOWN
  8                     || mFirstTouchTarget != null) {//Touch按下事件
  9                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 10                 if (!disallowIntercept) {
 11                     intercepted = onInterceptTouchEvent(ev);//判断消息是否需要被viewGroup拦截,这个方法我们可以覆写,
 12                                             //覆写生效的前提是 disallowIntercept 为FALSE,否则写了也没用
 13                     ev.setAction(action); // restore action in case it was changed
 14                 } else {//不允许拦截
 15                     intercepted = false;
 16                 }
 17             } else {
 18                 // There are no touch targets and this action is not an initial down
 19                 // so this view group continues to intercept touches.
 20         //这个操作不是一开始down事件,我们把它置为TRUE,拦截之
 21                 intercepted = true;
 22             }
 23 
 24             // Check for cancelation.
 25             final boolean canceled = resetCancelNextUpFlag(this)
 26                     || actionMasked == MotionEvent.ACTION_CANCEL;
 27 
 28                 //.........
 29 
 30                     final int childrenCount = mChildrenCount;//ViewGroup中子View的个数
 31                     if (newTouchTarget == null && childrenCount != 0) {
 32                         final float x = ev.getX(actionIndex);//获取坐标,用来比对
 33                         final float y = ev.getY(actionIndex);
 34                         // Find a child that can receive the event.
 35                         // Scan children from front to back.
 36                         final View[] children = mChildren;//获取viewgroup所有的子view
 37 
 38                         final boolean customOrder = isChildrenDrawingOrderEnabled();//子View的绘制顺序
 39                         ////从高到低遍历所有子View,找到能处理touch事件的child View
 40                         for (int i = childrenCount - 1; i >= 0; i--) {
 41                             final int childIndex = customOrder ?
 42                                     getChildDrawingOrder(childrenCount, i) : i;//根据Order获取子view
 43                             final View child = children[childIndex];
 44                             //判断是不是我们需要的View
 45                             if (!canViewReceivePointerEvents(child)
 46                                     || !isTransformedTouchPointInView(x, y, child, null)) {
 47                                 continue;
 48                             }
 49 
 50                             newTouchTarget = getTouchTarget(child);//从链表里找子view
 51                             if (newTouchTarget != null) {//找到子view
 52                                 // Child is already receiving touch within its bounds.
 53                                 // Give it the new pointer in addition to the ones it is handling.
 54                 //已经找到,循环结束,目标就是newTouchTarget
 55                                 newTouchTarget.pointerIdBits |= idBitsToAssign;
 56                                 break;
 57                             }
 58 
 59                     //.......
 60                         }
 61                     }
 62                 }
 63             }
 64 
 65             // Dispatch to touch targets.
 66             if (mFirstTouchTarget == null) {
 67                 // No touch targets so treat this as an ordinary view.
 68     /*dispatchTransformedTouchEvent方法中,如果child是null,那么就调用super.dispatchTouchEvent,
 69    *也就是ViewGroup的父类View的dispatchTouchEvent(如果我们在前面拦截了touch事件,那么就会这样处理),
 70    *如果不是null,则调用child.dispatchTouchEvent。
 71    **/
 72                 handled = dispatchTransformedTouchEvent(ev, canceled, null,
 73                         TouchTarget.ALL_POINTER_IDS);
 74             } else {
 75             //....
 76             }
 77         //........
 78         return handled;
 79     }
 80  /**
 81      * Transforms a motion event into the coordinate space of a particular child view,
 82      * filters out irrelevant pointer ids, and overrides its action if necessary.
 83      * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
 84      */
 85     private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
 86             View child, int desiredPointerIdBits) {
 87         final boolean handled;
 88  
 89         //……
 90  
 91         // Perform any necessary transformations and dispatch.
 92         if (child == null) {
 93             handled = super.dispatchTouchEvent(transformedEvent);
 94         } else {
 95             //……
 96             handled = child.dispatchTouchEvent(transformedEvent);
 97         }
 98  
 99         // Done.
100         //……
101         return handled;
102     }
我们来总结一下 ViewGroup 的 dispatchTouchEvent 的调用过程。
  1. 首先判断此 MotionEvent 能否被拦截,如果是的话,能调用我们覆写 onInterceptTouchEvent来处理拦截到的事件;如果此方法返回TRUE,表示需要拦截,那么事件到此为止,就不会传递到子View中去。这里要注意,onInterceptTouchEvent 方法默认是返回FALSE。
  2. 若没有拦截此Event,首先找到此ViewGroup中所有的子View,通过方法 canViewReceivePointerEvents和isTransformedTouchPointInView,对每个子View通过坐标(Event事件坐标和子View坐标比对)计算,找到坐标匹配的View。
  3. 调用dispatchTransformedTouchEvent方法,处理Event事件。
 1 //ViewGroup.java dispatchTransformedTouchEvent方法截取
 2         if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
 3             event.setAction(MotionEvent.ACTION_CANCEL);
 4             if (child == null) {//Event事件被截获,调用父类View的dispatchTouchEvent方法
 5                 handled = super.dispatchTouchEvent(event);
 6             } else {
 7                 handled = child.dispatchTouchEvent(event);//调用子View的dispatchTouchEvent方法
 8             }
 9             event.setAction(oldAction);
10             return handled;
11         }
  1. 假设这个子View是一个Button,会调用Button.dispatchTouchEvent 方法,Button和它的父类TextView都没有dispatchTouchEvent方法,只能继续看父类View了,其实最终调用的还是View.dispatchTouchEvent 方法。
  2. 我们继续分析View.dispatchTouchEvent 方法。mOnTouchListener 是OnTouchListener对象,由setOnTouchListener 方法设置;
 1 public boolean dispatchTouchEvent(MotionEvent event) {
 2         if (mInputEventConsistencyVerifier != null) {
 3             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
 4         }
 5 
 6         if (onFilterTouchEventForSecurity(event)) {
 7             //noinspection SimplifiableIfStatement
 8             ListenerInfo li = mListenerInfo;//View 内部类,管理一些listener
 9             if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
10                     && li.mOnTouchListener.onTouch(this, event)) {
11                 return true;
12             }
13 
14             if (onTouchEvent(event)) {
15                 return true;
16             }
17         }
18 
19         if (mInputEventConsistencyVerifier != null) {
20             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
21         }
22         return false;//没有消费掉,只能返回false,让调用者来处理了。
23     }
24     /**
25      * Register a callback to be invoked when a touch event is sent to this view.
26      * @param l the touch listener to attach to this view
27      */
28     public void setOnTouchListener(OnTouchListener l) {
29         getListenerInfo().mOnTouchListener = l;
30     }
若当前ListenerInfo 方法初始化并且 li.mOnTouchListener 的值不为空且ENABLE掩码为Enable,那么调用mOnTouchListener(this,event)方法。boolean onTouch(View v, MotionEvent event) 这个方法是在View的内部接口 OnTouchListener中的,是一个空方法,需要用户自己来实现。拿一个Button来举例;我们覆写的onTouch()方法在这里被调用。
1 button.setOnTouchListener(new OnTouchListener() {
2             @Override
3             public boolean onTouch(View v, MotionEvent event) {
4               //实现自己的功能
5                 return true;
6             }
7         });
  1. 若onTouch方法返回true,则表示被消费,不会继续传递下去;返回false,表示时间还没被消费,继续传递到 onTouchEvent 这个方法里。
 1 public boolean onTouchEvent(MotionEvent event) {
 2         //……
 3         if (((viewFlags & CLICKABLE) == CLICKABLE ||
 4                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
 5             switch (event.getAction()) {
 6                 case MotionEvent.ACTION_UP:
 7                     //……
 8                     //如果没有触发长按事件,手指动作是up,则执行performClick()方法
 9                         if (!mHasPerformedLongPress) {
10                             // This is a tap, so remove the longpress check
11                             removeLongPressCallback();
12  
13                             // Only perform take click actions if we were in the pressed state
14                             if (!focusTaken) {
15                                 // Use a Runnable and post this rather than calling
16                                 // performClick directly. This lets other visual state
17                                 // of the view update before click actions start.
18                                 //这里判断并去执行单击事件
19                                 if (mPerformClick == null) {
20                                     mPerformClick = new PerformClick();
21                                 }
22                                 if (!post(mPerformClick)) {
23                                     performClick();
24                                 }
25                             }
26                         }
27  
28                     break;
29                 case MotionEvent.ACTION_DOWN:
30                         //……
31                         //是否触发长按事件是在这里判断的,具体细节我就不贴出来了
32                         checkForLongClick(0);
33                         //……
34                     break;
35                     //……
36             }
37             return true;
38         }
39  
40         return false;
41     }
若状态不是CLICKABLE,那么会直接跳过判断执行return false,这意味着后续的touch事件不会再传递过来了。而大家注意看,只要是CLICKABLE,那么无论case哪个节点,最后都是return true,这样就保证了后续事件可以传递过来。 很明显在onTouchEvent 方法里面,主要就是判断应该执行哪个操作,是长按还是单击,然后去执行对应的方法。我们看看如果是单击,执行的方法:
 1 public boolean performClick() {
 2         //……
 3  
 4         ListenerInfo li = mListenerInfo;
 5         if (li != null && li.mOnClickListener != null) {
 6             //播放点击音效
 7             playSoundEffect(SoundEffectConstants.CLICK);
 8             //执行onClick方法
 9             li.mOnClickListener.onClick(this);
10             return true;
11         }
12  
13         return false;
14     }
其实就是调用了我们OnClickListener里面的onClick方法。所以说,当onTouch() 和 onClick()都存在时候,肯定是先执行onTouch,之后再执行onClick;如果onTouch 把事件截获直接return true,那么 onClick 方法就不会执行了。 到这里,整个touch事件的传递过程我们就分析完了。
用户点击屏幕产生Touch(包括DOWN、UP、MOVE,本文分析的是DOWN)事件 -> InputManager -> WindowManagerService.dispatchPointer() -> IWindow.Stub -> ViewRoot.dispatchPointer() -> PhoneWindow.DecorView.dispatchTouchEvent() -> Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent -> PhoneWindow.DecorView.superDispatchTouchEvent -> ViewGroup.dispatchTouchEvent() -> ViewGroup.dispatchTransformedTouchEvent() -> 子View.dispatchTouchEvent() -> 子View.onTouch() -> 子View.onTouchEvent() -> 事件被消费结束。(这个过程是由上往下传导)
如果事件没有被子View消费,也就是说子View的dispatchTouchEvent返回false,此时事件由其父类处理(由下往上传导),最后到达系统边界也没处理,就将此事件抛弃了。

有用的参考

  1. Android FrameWork——Touch事件派发过程详解
  2. android的窗口机制分析------事件处理
  3. Android事件分发机制完全解析,带你从源码的角度彻底理解(下)