您的位置:首页 > 移动开发 > Android开发

《Android内核剖析》读书笔记 第13章 View工作原理【消息类型与按键消息派发】

2013-06-25 18:38 239 查看
View是android系统的三大核心基础组件之一(另外两个分别是Ams和Wms),我们经常提到的Activity/Service/Content Provider/Broadcast Receiver这是应用开发层面的四大组件;
View提供了对页面展示各种元素的一种抽象,android系统中用到的所有UI控件(比如按钮、文本框等)都是继承于View类,他主要完成两块核心功能,其一:针对各种按键和触摸屏输入消息提供相应的处理机制;其二:完成各种UI控件在Canvas画布上的绘制、以及各种条件下触发的重绘;
用户消息类型
对于用户通过键盘或者触摸屏等输入设备产生的消息是不会直接进入到View系统中的,而是首先被消息处理前端做了一层拦截和消息码的转换,完成从原始的硬件码值到操作系统所能识别的码值转换,之后Wms便会把相应的消息经过一些规则的判断筛选,从而把消息发送给相应的应用程序窗口,到了目标窗口之后,就属于应用程序的内部处理逻辑了,这就是我们本章要介绍的View系统需要完成的功能;经过转换的消息大致分为三类:按键消息KeyEvent、触摸消息MotionEvent、轨迹球消息Trackball,随着现在手机硬件的发展,目前轨迹球已完全被废弃,物理按键也正在逐步被弱化,但为了保持应用的向下兼容,一些厂商开始采取触摸屏点击来模拟按键消息;

按键消息KeyEvent,该类定义了按键消息包含的各种参数,核心API接口包括:

getAction():返回按键具体动作,分别为 ACTION_DOWN/
ACTION_UP/ ACTION_MULTIPLE;
getKeyCode():返回按键代码,我们常用的包括电源键、声音大小调节、静音键、返回键、主页键、菜单键等,均由对应常量值标示;
getRepeat():返回按键从按下后重复的次数,他等于屏幕上显示的字符数-1;当按键释放后返回0;
要特别注意下,当按键被按下到开始重复时,从1到2这个过程会用比较多的时间,而之后的2-3,3-4等用时都是一样的,且小于1-2的时间。这种设计在View系统内部自动处理,对于开发者而言是完全透明的,设计的原因是为了更好的用户体验,因为用户在第一次按下时可能只是想完成一个点击操作,而不想触发重复按键,若我们响应太快则可能触发重复按键逻辑,这明显与用户预期想违背,但当用户一直按着不放,此时用户的需求已明确是长按,所以此时产生重复按键消息与用户预期一致;
KeyEvent.Callback接口:该接口定义了按键消息处理时的回调方法,比如onKeyDown/onKeyLongPress/onKeyUp等;

实现该接口的主要有View和Activity,在后面的按键消息处理流程中会涉及到;

另外一个需要特别注意的是:PhoneWindow虽然没有实现KeyEvent.Callback接口,但他也有onKeyDown/onKeyUp方法用来处理一些特殊逻辑,具体详见按键消息处理流程;

触摸消息MotionEvent,该类定义了和触摸相关的消息参数,核心API接口包括:

getAction():获取消息动作,具体类型很多,因为目前触摸屏均支持多点触控,最常见包括 ACTION_DOWN/
ACTION_UP/ ACTION_MOVE;
getEventTime()和getDownTime():前者获取消息发生的时间,后者获取DOWN消息发生的时间;
getX(.)和getY(.):返回触摸点对应的X/Y坐标,若无参数则返回第一个触摸点的坐标,对于多点触摸,参数index则代表具体的点,从0开始;
要特别注意:触摸一般都存在抖动问题,比如用户按下屏幕时,自以为手指没有移动,但实际上却产生了MOVE事件,用户在处理触摸消息是需要对这种情况特别注意,比如可以加入MOVE事件前后移动距离的判断,避免底层硬件高敏感度给用户体验造成困扰;

按键的两种长按监测

消息处理前端的“长按”:前面已提到这是为了消除用户生理反应时间,为避免混淆可以称之为“生理长按”,他具体指的是用户按下按键时产生的前2个DOWN消息之间的时间间隔,具体值在底层C++源码中定义,一般来说应用开发者是不需要关心该消息的;

体现在代码中若要响应“生理长按”消息,则需要重载View类中的onKeyLongPress()方法;
普通的“长按”:开发者经常接触的按键消息,具体指的是生理长按之后的2个DOWN消息之间的时间间隔,默认值是在ViewConfiguration.
DEFAULT_LONG_PRESS_TIMEOUT中定义的500ms;

体现在代码中若要响应该“长按”消息,则可以对需要响应该事件的View对象执行setOnLongClickListener(.),参数为实现android.view.View.OnLongClickListener接口的实现类实例;
“长按”是如何实现触发的?经过“生理长按”之后,系统默认会发送一个异步消息,指定该消息在指定时间(默认为500ms)后执行一段Runnable代码,而这段代码就是回调为视图设置的OnLongClickListener接口实例的
onLongClick(.)方法;若未到指定时间用户已经松开按键,则系统将自动删除之前发送的那个异步消息,这样自然也就不会执行长按逻辑了;

按键消息总体派发流程
之前已经讲到,当需要页面展示时,会执行到ViewRootImpl.setView(…),在这里会调用windowSession完成向Wms服务端提交新建窗口的请求,同时系统还注册了当窗口有消息时的回调接口实例WindowInputEventReceiver,当有消息产生时会先由Wms进行一定的预处理,核心是对一些系统级窗口下按键的消息处理(比如当屏幕处于锁屏窗口时按Home键,此时是不能启动Launcher应用的;再比如对Home键的长按监测,若对Home长按则会显示最近加载的应用列表等),具体代码位于PhoneWindowManager.interceptKeyBeforeDispatching(…);
之后就会回调至WindowInputEventReceiver,然后进入deliverInputEvent(.)方法处理所有的输入消息,包括按键、触摸屏等,然后根据不同分类执行不同的具体逻辑,对于按键消息进入deliverKeyEvent(.),其具体执行逻辑如下:

执行 mView.dispatchKeyEventPreIme(event),即处理输入法之前的一些回调;因为对于View系统而言,如果有输入法窗口存在,则先将按键消息派发到输入法窗口,只有当输入法窗口不处理该消息时,才会把消息派发到真正的视图View;对于有输入法窗口显示并且开发者想在输入法真正处理消息之前干点什么,则可以重载View中的该方法;
若有输入法窗口,则执行 imm.dispatchKeyEvent(…),让输入法处理相关消息;
执行 deliverKeyEventPostIme(.),将消息派发到真正的视图View,具体就是调用
mView.dispatchKeyEvent(event);

特别注意:这里的mView变量具体指的是PhoneWindow.DecorView实例,所以执行的方法并不是基类View中的dispatchKeyEvent方法!

其PhoneWindow.DecorView.dispatchKeyEvent(.)中的具体逻辑包括:

处理系统快捷键,比如最常见的是触发Menu键调出对应的Panel菜单项;
若消息还未被处理,判断该PhoneWindow实例是否有Window.Callback实例(该实例在PhoneWindow初始化时设置),若有一般为当前窗口所对应的Activity;

不存在Activity回调实例,执行DecorView.super.dispatchKeyEvent(.)方法,DecorView的父类一次是FrameLayout --> ViewGroup --> View,所以最终会执行到View.dispatchKeyEvent(.);

判断当前View有没有设置过 OnKeyListener接口实例,若有,则调用实例的
onKey(…)方法处理消息;
若没有设置过,则执行View.onKeyDown/onKeyUp回调方法;

在这里将处理单击Click和长按LongClick两种事件,具体方案之前已经提到,就是会在Down事件产生时调用
postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset);产生一个延时异步任务;并根据用户长按是否超过该时间间隔选择执行还是删除该异步任务;
注意:在ViewGroup中处理按键消息时,是首先调用super.dispatchKeyEvent(),即优先让父视图处理,若父视图没有消耗,才调用子视图mFocusedView.dispatchKeyEvent()进行处理;

如存在Activity回调实例,执行Activity.dispatchKeyEvent(.)方法;

执行onUserInteraction();可以在自己的Activity中重载该方法以便在消息被处理之前做点什么;
执行PhoneWindow. superDispatchKeyEvent(.);其实是转交执行DecorView.super.dispatchKeyEvent(.),这个步骤和上面不存在Activity回调实例时的逻辑是一致的,即不管如何,按键信息都会首先让View系统自身处理;
若View系统自身未处理该消息,则执行Activity.onKeyDown/onKeyUp回调方法;

若消息还未被处理,调用PhoneWindow.this.onKeyDown/Up(.)方法,主要是处理音量键、回退键、菜单键、搜索键等;

若消息已被消费,执行 finishInputEvent(…)完成一次消息派发;

以上内容若有转载,请注明出处,欢迎访问老唐的专栏http://blog.csdn.net/sfdev
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: