什么是输入事件?

我们知道,运行android系统的设备本质上是一台计算机,使用者在和计算机进行交互的时候可以抽象成简单的对计算机的输入和输出(IO)。那么对于运行在计算机上的操作系统来说,操作系统在与使用者进行交互的时候起始也是可以抽象成对外界的输入进行处理,然后在输出返还给使用者。本文只讨论的是android系统中的“输入事件”(因为本文讨论的都是输入事件,所以以下简称“输入事件”为“事件”)相关的内容。

根据以上描述,我们就可以回答上面的问题了:

事件是计算机设备接收到的输入信息。

有了以上的定义,我们先来研究下事件是怎么产生的?

在Android开发

系统中,事件是由用户的动作触发的,进而在android硬件层引起硬件状态的变化,android底层会将硬件的状态的变化封装成更加高级的类对象并传递到android 的Framework层,Framework会继续把事件传递给应用层(确切地说是应用层里面应用开发者编写的具体的View的子类对象),最终应用层层会根据事件来进行响应并将响应结果通过对硬件的调用反馈给用户。在以上的整个过程中,应用开发者只需要关注其中的一小部分就可以了,那就是事件从Framework层传递到应用层之后我们该怎么处理这些事件。当然,本文也主要讨论这些内容。

借用以下android 架构图来展示下事件从底层到应用层经历了多少过程。android架构图

注意:事件的传递并不是只传递一个单一的事件的,每一次用户的动作都会产生一个有序的事件列表,这个有序的事件列会被按顺序一次传递到应用层。

输入事件类型

android中所有的事件被抽象成了一个Java类,InputEvent。它有两个直接子类,MotionEvent和 KeyEvent,分别代表了动作事件(通常由触摸屏所产生,也是最常见的)的事件和按键所产生的事件。但是事实上InputEvent不止有这两种。因为android系统可运行的设备类型非常丰富,在不同的设备上对应不同的硬件,肯定也就回有不同的输入事件了,InputEvent按照事件的输入来源不同可以分为以下几类(非全部):

Sensor InputEvent(传感器输入事件)

Hook InputEvent(耳机Hook键输入事件)

On InputEvent(开机键输入事件)

Touch Screen InputEvent(触摸屏输入事件,也是本文主要介绍的)

Key InputEvent(按键输入事件,Home/Menu/Back,电视遥控器按键等)

Hover InputEvent(鼠标动作输入事件)

一般来说,android系统的运行设备是手机或者平板电脑,它们都会有触摸屏,最常见的输入事件还是Touch Screen InputEvent了。本文将会主要介绍TouchScreen InputEvent 和 Key InputEvent。android系统的输入设备列表参考这里:输入设备

Touch Screen InputEvent的处理和传递

Touch Screen InputEvent基础

Touch Screen InputEvent被封装成了一个java类-MotionEvent,它包含三个基础的事件,其它的事件都是在者三个事件的基础之上进行组合封装而成的,例如,click事件就是一个ACTION_DOWN 和一个 ACTION_UP的组合。

基础事件:

ACTION_DOWN   手指按到屏幕上

ACTION_MOVE   手指在屏幕上移动

ACTION_UP  手指从屏幕上离开

事件序列:

上文以及提到过,Touch Screen的事件并不是单个出现的,用户的每次交互都会产生一个事件序列,这个序列会以一个ACTION_DOWN开头,中间有0 或者 n个ACTION_MOVE事件,最后以一个ACTION_UP结尾。一个事件序列往往可以组成更加高级的事件,比如说点击事件、双击事件、手势事件等,View在消费这些事件的时候也会以一个独立的事件序列作为一个最小单位来消费,而不是一个个的基础事件。

所以,我们可以认识到,Touch Screen的事件是可以分为两种类型的:

基础类型:包含ACTION_DOWN、ACTION_MOVE、ACTION_UP三个。

复合类型:由基础类型的事件序列组合称一个代表了用户特定的输入信息的事件。

几个关键方法及其其作用

在软件开发中,当我们写下一个方法后,我们的目的可能是使用该方法完成某些功能,也有可能是为了程序的可扩展性等其它的理由。打个比方来说,在android的常用组件Activity类中,有一个方法叫做setContentView(),它的作用是给当前的Activity指定一个布局来显示,这样的方法就是我们刚提到的“来完成某些功能”的方法;Activity中还有一个叫做onCreate() 的方法,它的作用是一个占位的方法,表示现在是Activity正在创建的时机,可以在子类来重写该方法完成一些初始化工作,像这类方法并没有完成某些具体的功能,就是我们提到的“为了程序的可扩展性等其它的理由”。为了方便起见,在本文中我将会称第一种方法为“功能型方法”,称第二种为“结构型方法”。但是有些方法并不是简单地确认为某一种类型,它可能两种特性都包含。还以以上提到的Activity的onCreate方法为例,首先,它是个结构型方法,因为它的目的包括通知子类进行初始化工作,其次,它本身也进行了一部分对Activity的初始化,这样来说,他就也完成了某些功能,也可以说是一个功能型方法,事实上onCreate两者都是(以下简称该类方法为Both)。

在MotionEvent的处理和传递过程中有几个关键的方法,他们分别出现咋View、ViewGroup、Activity、Window等多个类中,但是名字则是完全一致,功能也是非常接近的。

以上提到了几个关键方法是MotionEvent事件分发和处理的关键所在,它们涉及到几个类:

Activity

Window

View

ViewGroup

那么,MotionEvent事件是如何在这几个类之间传递的呢?而且我们以及知道了事件是由Framework层传递到应用层的,那么究竟是谁从Framework层传递过来,又是谁在应用层接收到了呢?

通过查看android SDK的源码,可以发现是WindowManagerService这个关键类把事件从Framework层传递到应用层的,并且通过PhoneWindow的成员DecorView传递到了当前的Activity中(具体详情请参考Android FrameWork——Touch事件派发过程详解,本文不再赘述)。也就是说是WindowManagerService做了Framework层和应用层的桥梁,把MotionEvent事件传递给了当前的Activity,当前Activity是应用层最先接收到MotionEvent事件的类。

这时候,就可以理解为什么在Activity中有dispatchTouchEvent 和 onTouchEvent这两个类了,因为Activity是应用层事件分发的第一站。

我们已经了解到了MotionEvent是怎么通过Activity传递到View中的,而android的View层是一个拥有树状结构的View树,树的每一个节点也都是一个View,那么MotionEvent是怎么在View树内部传递的呢?

首先,与该事件相关的根View的dispatchTouchEvent会被调用,一次来在该View内分发事件,事件会被分发给该类的onTouchEvent方法、该View的OnTouchListener的onTouch方法、以及子View的dispatchTouchEvent方法。View会依据以上所提到的三个目标方法的返回值来觉得dispatchTouchEvent的最终返回值。

然后,当事件被分发到onTouchEvent方法中以后,该View会对事件进行处理,View的OnClickListener的onClick方法便是在这里调用的。

假如说该View是一个ViewGroup,那么它的onInterceptTouchEvent方法会在dispatchTouchEvent方法执行的时候被调用,并根据是否打断事件传递来觉得dispatchTouchEvent的返回值。

以下伪代码可以清晰地展示它们三者的关系:

Java

public booleandispatchTouchEvent(MontionEvent e){

boolean consume = false;

if(onInterceptTouchEvent(e)){

consume = onTouchEvent(e);

}else{

consume = child.dispatchTouchEvent(e);

}

return consume;

}

public booleandispatchTouchEvent(MontionEvent e){

boolean consume = false;

if(onInterceptTouchEvent(e)){

consume = onTouchEvent(e);

}else{

consume = child.dispatchTouchEvent(e);

}

return consume;

}

当然,事实上View中的dispatchTouchEvent要复杂的多,它需要处理的东西非常多,这里我们就只挑主要的介绍了。

MotionEvent事件序列是被当做一个整体来处理的,假如一个View不消耗一个ACTION_DOWN事件,那么随后的其它该序列的事件也将不会传递给它。

当一个View觉得要处理某个时间序列的话,该事件序列的所有子事件都将会被交给它处理。

ViewGroup的onInterceptTouchEvent默认返回false,也就是说ViewGroup默认是不拦截事件的。

Key InputEvent的处理和传递

Key InputEvent 基础

在应用层,Key InputEvent被封装成了一个叫做KeyEvent的类,它想MotionEvent一样,也是InputEvent的一个子类。总的来说,KeyEvent在应用层的传递方式和Motion Event是一致的。都是由WindowManagerService获取到事件,由DecorView调用ViewRoot,进而按照Activity-Window-View树的流程传递事件的。

类似于MotionEvent的关键方法,KeyEvent的关键方法分别是:

dispatchKeyEvent

onKeyEvent

onInterceptKeyEvent

在View树中的传递过程与Touch事件的一致,这里不再赘述。

以上所提到的只是Android开发

中事件分发机制的大致轮廓,具体的代码则要复杂的多,大家有时间的话可以研究下具体的源码,相信大家都会有很大的收获的。

Android系统输入事件分发详解的更多相关文章

  1. Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习

    package cc.aa; import android.os.Environment; import android.view.MotionEvent; import android.view.V ...

  2. Android事件分发详解(六)——ACTION_DOWN的消费验证

    MainActivity如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 3 ...

  3. Android系统--输入系统(十七)Dispatcher线程_分发dispatch

    Android系统--输入系统(十七)Dispatcher线程_分发dispatch 1. 回顾 InputRead线程从输入设备当中得到输入事件 对于读到输入事件稍作处理,比如紧急事件,来电时候按下 ...

  4. Android系统--输入系统(八)Reader线程_使用EventHub读取事件

    Android系统--输入系统(八)Reader线程_使用EventHub读取事件 1. Reader线程工作流程 获得事件 size_t count = mEventHub->getEvent ...

  5. Android系统--输入系统(十三)Dispatcher线程情景分析_Reader线程传递事件

    Android系统--输入系统(十三)Dispatcher线程情景分析_Reader线程传递事件 1. 输入按键 我们知道Android系统的按键分为三类:(1)Global Key;(2)Syste ...

  6. [Android新手区] SQLite 操作详解--SQL语法

    该文章完全摘自转自:北大青鸟[Android新手区] SQLite 操作详解--SQL语法  :http://home.bdqn.cn/thread-49363-1-1.html SQLite库可以解 ...

  7. Android系统--输入系统(九)Reader线程_核心类及配置文件

    Android系统--输入系统(九)Reader线程_核心类及配置文件 1. Reader线程核心类--EventHub 1.1 Reader线程核心结构体 实例化对象:mEventHub--表示多个 ...

  8. Android系统--输入系统(十)Reader线程_核心类及配置文件深入分析

    Android系统--输入系统(十)Reader线程_核心类及配置文件深入分析 0. 前言 个人认为该知识点阅读Android源代码会不仅容易走进死胡同,并且效果并不好,前脚看完后脚忘记,故进行总结, ...

  9. Android App优化之ANR详解

    引言 背景:Android App优化, 要怎么做? Android App优化之性能分析工具 Android App优化之提升你的App启动速度之理论基础 Android App优化之提升你的App ...

随机推荐

  1. java源码 -- AbstractMap

    AbstractMap抽象类实现了一些简单且通用的方法,本身并不难.但在这个抽象类中有两个方法非常值得关注,keySet和values方法源码的实现可以值的学习. 抽象类通常作为一种骨架实现,为各自子 ...

  2. JavaIO -- Reader 和 Writer

    一.简介 设计Reader和Writer继承层次结构主要是为了国际化.InputStream和OutStream流继承层次结构仅支持8位字节流,并不能很好的处理16位的Unicode字符.由于Unic ...

  3. python3 获取自建gitlab用户提交信息

    #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-12-03 14:20 # @Author : Anthony # @Emai ...

  4. 20190621-N皇后

    N皇后 难度分类 困难 题目描述 n皇后问题研究的是如何将 n个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 上图为 8 皇后问题的一种解法. 给定一个整数 n,返回所有不同的 n  ...

  5. golang goroutine进行通信 channel

    1.channel的读取与声明 //goroutine之间利用channel进行通信 package main import ( "fmt" "time" ) ...

  6. go 构造切片slice

    定义切片 make([]int, 5)  长度和容量均为5 make([]int, 0, 5) 长度为0 容量为0 切片 slice2[3:5] 对slice2进行切片返回 第3 4 两个元素 不包含 ...

  7. Django中的Object Relational Mapping(ORM)

    ORM 介绍 ORM 概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术. 简单的说,ORM是通过使用 ...

  8. 音视频入门-02-RGB拼图

    * 音视频入门文章目录 * 图片 & 像素点 & RGB 平时浏览的图片看不出像素点: 图片放大时,可以看出图片是一个个像素点组成的: 每个像素点的颜色可以用 RGB 表示: RGB ...

  9. poj 3617 弱鸡贪心

    比赛的时候扣了一道贪心的题目,不会写,,现在补一补一些基础的贪心. 题意:给定一个字符串s,要求按下列操作生成一个新串t--每次从s串中的最前和最后取一个字符给t,要求生成的t字典序最小. 题解:由于 ...

  10. C++ ifstream ofstream 注意事项

    很久没写C++,已经完全不会写了... 在使用ifstream读取一个二进制文件时,发现读取的内容和源文件不相同,导致数据解析失败,于是尝试把用ifstream读取的内容用ofstream写入另一个文 ...