事件处理就是定义和响应用户的操作事件(按键、触屏等),也是控件开发中的一个难题。比如onIntrceptTouchEvent,onTouchEvent,onClick,onLongClick等一系列的方法容易让人混淆。为了厘清这些难题,我们有必要看看整个事件处理的流程。
2.在InputQueue对象的dispatchKeyEvent函数中调用ViewRootImpl对象内部的 inputHandler对象的handleKey函数;
3.接着又调用 ViewRootImpl对象的dispatchKey函数;dispatchKey函数通过enqueueInputEvent向ViewRootImpl对象的事件处理线程发送异步消息;ViewRootImpl对象的事件处理函数handleMessage根据消息类型进行相应处理,这里调用的是 deliverKeyEvent函数;deliverKeyEvent函数首先调用主视图的dispatchKeyEventPreIme函数,接着调用deliverKeyEventPostIme函数;deliverKeyEventPostIme函数调用主视图的dispatchKeyEvent函数在视图树中派发事件;dispatchKeyEvent函数派发完后返回进行其它处理如聚焦切换等;
4. 主视图DecorView的dispatchKeyEvent函数首先判断是否进行快捷键调用,然后调用主视图绑定的ACTIVITY的dispatchKeyEvent函数,如果没有处理事件,则根据按键状态调用PhoneWindow的onKeyDown函数或onKeyUp函数;
5.PhoneWindow的superDispatchKeyEvent在视图树(DecorView)中派发事件,DecorView继承于ViewGroup,因此ViewGroup的dispatchKeyEvent函数被调用,再通过ViewGroup对象的mFocused(焦点子视图)成员向下一级焦点视图派发事件。这样一级级向各个焦点ViewGroup和Focuse View视图派发事件,完成整个视图树的事件派发。
最后,总结一下整个窗口管理的类图:
android对事件的管理主要有完成按键、触摸板、鼠标等输入设备的输入,向焦点窗口和焦点视图的事件派发,事件的插入,事件的过滤,事件的拦截等。服务端部分主要完成输入设备事件的读取、事件的映射、事件的插入、事件的过滤、事件的拦截等功能;客户端部分主要完成事件向焦点窗口和焦点视图的派发。具体来说,按键、触屏等事件是经由WindowManagerService获取,并通过共享内存和管道的方式传递给ViewRoot,ViewRoot再dispatch给Application的View。这里的进程间通信没有采用Binder机制,因为要即时响应用户事件。管道通信,就是其中一个进程在管道的读端等待新的内空可读,另一个进程在管道的写端写入新的内容以唤醒在管道读端等待的进程。
图1 内存共享
图2 管道通信
在ViewRoot和WMS(WindowManagerService)建立起连接之前首先会创建一个InputChannel对象,同样的WMS端也会创建一个InputChannel对象,不过WMS的创建过程是在ViewRoot调用add()方法时调用的。这一对InputChannel中实现了一组全双工管道。在创建InputChannel对的同时,会申请共享内存,并向2个InputChannel对象中各自保存一个共享内存的文件描述符。
WindowManagerService--->ViewRoot方向的管道通信,表示WMS通知ViewRoot有新事件被写入到共享内存;
ViewRoot-->WindowManagerService方向的管道通信,表示ViewRoot已经消化完共享内存中的新事件,特此通知WMS。
由于系统中存在不只一对通信管道,所以需要对这些管道统一管理。ViewRoot端的管道一般情况下会注册到一个NativeInputQueue对象中(这是一个Native的对象,而JAVA端的InputQueue类仅仅是提供了一些static方法与NativeInputQueue通信),WMS端注册在InputManager对象中(实际上创建了C++层的NativeInputManager,它整个业务的核心又分InputReader和InputDispatcher两个模块)。下面我们看事件处理的整个类图和时序图。
图3 输入系统服务端类图
图4 客户端事件派发类图
图5 客户端事件派发时序图
我们重点关注客户端的事件派发。以key事件为例,在客户端收到服务端管道发送的事件时,就触发客户端管道注册时注册的回调函数(图27 NativeInputQueue对象的handleReceiveCallback函数):
1.首先NativeInputQueue对象的
handleReceiveCallback根据回调来的接收管道参数receiveFd在NativeInputQueue队列中找到connectionIndex,根据connectionIndex找到对应的connection对象;然后调用connection对象中的inputConsumer对象的receiveDispatchSignal函数读取服务端通过管道发送的DispatchSignal信号,并判断发送信号标志是否正确;接着调用inputConsume对象的consume函数从共享内存中读取mSharedMessage消息,并根据消息类型构造事件,如按键对应的keyEvent事件,并转换为JAVA事件;然后通过JNI回调JAVA对象
InputQueue的dispatchKeyEvent函数;
2.在InputQueue对象的dispatchKeyEvent函数中调用ViewRootImpl对象内部的 inputHandler对象的handleKey函数;
3.接着又调用 ViewRootImpl对象的dispatchKey函数;dispatchKey函数通过enqueueInputEvent向ViewRootImpl对象的事件处理线程发送异步消息;ViewRootImpl对象的事件处理函数handleMessage根据消息类型进行相应处理,这里调用的是 deliverKeyEvent函数;deliverKeyEvent函数首先调用主视图的dispatchKeyEventPreIme函数,接着调用deliverKeyEventPostIme函数;deliverKeyEventPostIme函数调用主视图的dispatchKeyEvent函数在视图树中派发事件;dispatchKeyEvent函数派发完后返回进行其它处理如聚焦切换等;
4. 主视图DecorView的dispatchKeyEvent函数首先判断是否进行快捷键调用,然后调用主视图绑定的ACTIVITY的dispatchKeyEvent函数,如果没有处理事件,则根据按键状态调用PhoneWindow的onKeyDown函数或onKeyUp函数;
5.PhoneWindow的superDispatchKeyEvent在视图树(DecorView)中派发事件,DecorView继承于ViewGroup,因此ViewGroup的dispatchKeyEvent函数被调用,再通过ViewGroup对象的mFocused(焦点子视图)成员向下一级焦点视图派发事件。这样一级级向各个焦点ViewGroup和Focuse View视图派发事件,完成整个视图树的事件派发。
6.如果视图树没有处理事件则调用event.dispatch函数派发事件由ACTIVITY对象本身处理,ACTIVITY对象的事件回调接口在这里被调用;
下面,我们再以Touch事件为例,从源码角度分析
Activity、View和ViewGroup的事件处理流程:
class Activity {
...
//DecorView把事件分发过来
public
boolean
dispatchTouchEvent(MotionEvent ev) {
if
(ev. getAction() == MotionEvent.
ACTION_DOWN
) {
onUserInteraction();
}
//分发给PhoneWindow,再分发给DecorView
if
(g
t
eWindow().superDispatchTouchEvent(ev)) {
return
true
;
}
//如果没处理,自己处理
return
onTouchEvent(ev);
}
public
boolean
onTouchEvent (MotionEvent event) {
if
(
mWindow
.shouldCloseOnTouch(
this
, event)) {
finish();
return
true
;
}
return
false
;
}
...
}
------------------------------------------------------------------------------------------------
class
PhoneWindow{
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor会调用父类FrameLayout的父类ViewGroup的方法
return mDecor.superDispatchTouchEvent(event);
}
return mDecor.superDispatchTouchEvent(event);
}
}
------------------------------------------------------------------------------------------------
class
ViewGroup{
public
boolean
dispatchTouchEvent(MotionEvent ev) {
...
if
(onFilterTouchEventForSecurity(ev)) {
final
int
action = ev.getAction();
final
int
actionMasked = action & MotionEvent.
ACTION_MASK
;
// Handle an initial down.
if
(actionMasked == MotionEvent.
ACTION_DOWN
) {
//当新开始一个touch事件时,抛弃先前的touch状态
//当app切换,发生ANR或一些其他的touch状态发生时,framework会丢弃或取消先前的touch状态
//当app切换,发生ANR或一些其他的touch状态发生时,framework会丢弃或取消先前的touch状态
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//检查拦截事件
final
boolean
intercepted;
if
(actionMasked == MotionEvent.
ACTION_DOWN
||
mFirstTouchTarget
!=
null
) {
final
boolean
disallowIntercept = (
mGroupFlags
&
FLAG_DISALLOW_INTERCEPT
) != 0;
//
这个值默认是false, 可通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)设置
if
(!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);//是否拦截
ev.setAction(action);
// restore action in case it was changed
}
else
{
intercepted =
false
;
}
}
else
{
//没有touch事件的传递对象,同时touch动作不是初始动作down,ViewGroup就拦截这个事件
intercepted =
true
;
}
...
if
(!canceled && !intercepted) {
if
( actionMasked == MotionEvent.
ACTION_DOWN
|| (split && actionMasked == MotionEvent.
ACTION_POINTER_DOWN
)
|| actionMasked == MotionEvent.
ACTION_HOVER_MOVE
) {
...
final
int
childrenCount =
mChildrenCount
;
if
(newTouchTarget ==
null
&& childrenCount != 0) {
final
float
x = ev.getX( actionIndex);
final
float
y = ev.getY( actionIndex);
// Scan children from front to back.
final
View[] children =
mChildren
;
final
boolean
customOrder = isChildrenDrawingOrderEnabled();//是否自定义了子view的顺序
for
(
int
i = childrenCount - 1; i >= 0; i--) {
//2.遍历子view,把事件传给获焦的子view
final
int
childIndex = customOrder ?
getChildDrawingOrder( childrenCount, i) : i;
final
View child = children[childIndex];
if
(! canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child,
null
)) {
//如果子view不接收pointer event或超出其区域,直接找下一个
continue
;
}
newTouchTarget = getTouchTarget(child);//通过getTouchTarget去查找View是否在TouchTarget中了。
if
(newTouchTarget !=
null
) {
// 若子View处于touch目标中,同时已经接收了touch事件,则为器增加新的touch点??
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.
pointerIdBits
|= idBitsToAssign;
break
;
}
if
(dispatchTransformedTouchEvent(ev,
false
, child, idBitsToAssign)) {
//分发给子View,看是否处理了这个事件
//把MotionEvent的点坐标转换到子View的坐标系中,为ViewGroup创建一个新TouchTarget,TouchTarget包含了子View
mLastTouchDownTime
= ev.getDownTime();
mLastTouchDownIndex
= childIndex;
mLastTouchDownX
= ev.getX();
mLastTouchDownY
= ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget =
true
;
break
;
}
}
}
// 没有发现接收event的子View,把Touch点赋给最早添加到TouchTarget链中的对象
if
(newTouchTarget ==
null
&&
mFirstTouchTarget
!=
null
) {
newTouchTarget =
mFirstTouchTarget
;
while
(newTouchTarget.
next
!=
null
) {
newTouchTarget = newTouchTarget.
next
;
}
newTouchTarget.
pointerIdBits
|= idBitsToAssign;
}
}
}
// 分发到触摸对象Dispatch to touch targets.
if
(
mFirstTouchTarget
==
null
) {
//没有触摸的子view,就把自己当成一般view,自己处理
handled = dispatchTransformedTouchEvent(ev, canceled,
null
,
TouchTarget.
ALL_POINTER_IDS
);
}
else
{
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor =
null
;
TouchTarget target =
mFirstTouchTarget
;
while
(target !=
null
) {
final
TouchTarget next = target.
next
;
//如果已经处理,直接忽略
if
(alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled =
true
;
}
else
{
final
boolean
cancelChild = resetCancelNextUpFlag(target.
child
)
|| intercepted;
//分发到子View对象
if
(dispatchTransformedTouchEvent(ev, cancelChild,
target.
child
, target.
pointerIdBits
)) {
handled =
true
;
}
if
(cancelChild) {
if
(predecessor ==
null
) {
mFirstTouchTarget
= next;
}
else
{
predecessor.
next
= next;
}
target.recycle();
target = next;
continue
;
}
}
predecessor = target;
target = next;
}
}
// 若在触摸点发生了up或cancel,则更新TouchTarget链表
if
(canceled
|| actionMasked == MotionEvent.
ACTION_UP
|| actionMasked == MotionEvent.
ACTION_HOVER_MOVE
) {
resetTouchState();
}
else
if
(split && actionMasked == MotionEvent.
ACTION_POINTER_UP
) {
final
int
actionIndex = ev.getActionIndex();
final
int
idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if
(!handled && mInputEventConsistencyVerifier !=
null
) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return
handled;
}
/**如果子view不处理事件,就交给viewgroup自己处理
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private
boolean
dispatchTransformedTouchEvent(MotionEvent event,
boolean
cancel,
View child,
int
desiredPointerIdBits) {
final
boolean
handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final
int
oldAction = event.getAction();
if
(cancel || oldAction == MotionEvent.
ACTION_CANCEL
) {
event.setAction(MotionEvent.
ACTION_CANCEL
);
if
(child ==
null
) {
handled =
super
.dispatchTouchEvent(event);//如果没有子View处理,就自己处理
}
else
{
handled = child.dispatchTouchEvent(event);//分发给子View处理
}
event.setAction(oldAction);
return
handled;
}
// Calculate the number of pointers to deliver.
final
int
oldPointerIdBits = event.getPointerIdBits();
final
int
newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if
(newPointerIdBits == 0) {
return
false
;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final
MotionEvent transformedEvent;
if
(newPointerIdBits == oldPointerIdBits) {
if
(child ==
null
|| child.hasIdentityMatrix()) {
if
(child ==
null
) {
handled =
super
.dispatchTouchEvent(event);
}
else
{
final
float
offsetX = mScrollX - child.mLeft;
final
float
offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return
handled;
}
transformedEvent = MotionEvent. obtain(event);
}
else
{
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if
(child ==
null
) {
handled =
super
.dispatchTouchEvent(transformedEvent);
}
else
{
final
float
offsetX = mScrollX - child.mLeft;
final
float
offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if
(! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return
handled;
}
public
boolean
onInterceptTouchEvent (MotionEvent ev) {//自定义ViewGroup重载此方法
return
false
;
}
}
------------------------------------------------------------------------------------------------
class View{
public
boolean
dispatchTouchEvent(MotionEvent event) {
...
if
(onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li =
mListenerInfo
;
if
(li !=
null
&& li.
mOnTouchListener
!=
null
&& (
mViewFlags
&
ENABLED_MASK
) ==
ENABLED
&& li.
mOnTouchListener
.onTouch(
this
, event)) {//如果设置了OnTouchListener就调onTouch
return
true
;
}
if
(onTouchEvent(event)) {//如果没有,就发给onTouchEvent
return
true
;
}
}
...
return
false
;
}
public
void
setOnTouchListener (OnTouchListener l) {
getListenerInfo().
mOnTouchListener
= l;
}
public
boolean
onTouchEvent(MotionEvent event) {//自定义view重载此方法
final
int
viewFlags =
mViewFlags
;
...
//如果是disable的view,同时是
clickable或long_clickable的,会消费事件,但不处理
if
((viewFlags &
ENABLED_MASK
) ==
DISABLED
) {
if
(event.getAction() == MotionEvent.
ACTION_UP
&& (
mPrivateFlags
&
PFLAG_PRESSED
) != 0) {
setPressed(
false
);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return
(((viewFlags &
CLICKABLE
) ==
CLICKABLE
||
(viewFlags &
LONG_CLICKABLE
) ==
LONG_CLICKABLE
));
}
//如果是enable的view,触摸区域发生变化了(不是view实际大小),就在设置
setTouchDelegate(TouchDelegate
//delegate)来处理
if
(
mTouchDelegate
!=
null
) {
if
(
mTouchDelegate
.onTouchEvent(event)) {
return
true
;
}
}
// 如果是enable的view,同时是clickable或long_clickable的,处理事件
if
(((viewFlags &
CLICKABLE
) ==
CLICKABLE
||
(viewFlags &
LONG_CLICKABLE
) ==
LONG_CLICKABLE
)) {//不是clickable返回false
switch
(event.getAction()) {
//
ACTION_DOWN:如果不处理,返回false,事件会向上回传。只有接收了down事件才会产生后续事件
//
ACTION_MOVE:不回传,
//
ACTION_CANCLE:不回传,在ACTION_DOWN事件之后,后续事件被父控件拦截,此控件就会收到cancle事件
case
MotionEvent.
ACTION_UP
:
boolean
prepressed = (
mPrivateFlags
&
PFLAG_PREPRESSED
) != 0;
if
((
mPrivateFlags
&
PFLAG_PRESSED
) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean
focusTaken =
false
;
if
(isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if
(prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(
true
);
}
if
(!
mHasPerformedLongPress
) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if
(!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if
(
mPerformClick
==
null
) {
mPerformClick
=
new
PerformClick();
}
if
(!post(
mPerformClick
)) {
performClick();
//处理clikable事件
}
}
}
if
(
mUnsetPressedState
==
null
) {
mUnsetPressedState
=
new
UnsetPressedState();
}
if
(prepressed) {//处理长按事件
postDelayed(
mUnsetPressedState
,
ViewConfiguration.getPressedStateDuration());
}
else
if
(!post(
mUnsetPressedState
)) {
// If the post failed, unpress right now
mUnsetPressedState
.run();
}
removeTapCallback();
}
break
;
case
MotionEvent.
ACTION_DOWN
:
mHasPerformedLongPress
=
false
;
if
(performButtonActionOnTouchDown(event)) {
break
;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean
isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if
(isInScrollingContainer) {
mPrivateFlags
|=
PFLAG_PREPRESSED
;
if
(
mPendingCheckForTap
==
null
) {
mPendingCheckForTap
=
new
CheckForTap();
}
postDelayed(
mPendingCheckForTap
, ViewConfiguration.getTapTimeout());
}
else
{
// Not inside a scrolling container, so show the feedback right away
//
refreshDrawableState()-->
drawableStateChanged()-->
//
Drawable.
setState(getDrawableState())
-->
onStateChange(
int
[] stateSet)
setPressed(
true
);
checkForLongClick(0);
}
break
;
case
MotionEvent.
ACTION_CANCEL
:
setPressed(
false
);
removeTapCallback();
removeLongPressCallback();
break
;
case
MotionEvent.
ACTION_MOVE
:
final
int
x = (
int
) event.getX();
final
int
y = (
int
) event.getY();
//当手指在View上面滑动超过View的边界
if
(!pointInView(x, y,
mTouchSlop
)) {
// Outside button
removeTapCallback();
if
((
mPrivateFlags
&
PFLAG_PRESSED
) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(
false
);
}
}
break
;
}
return
true
;
}
return
false
;
}
}
经过以上分析,我们可以总结几点:
2.事件处理方式有:监听和回调。监听的处理逻辑可写在Activity中,便于调用context资源;回调的处理逻辑写在该view中,适合较为固定的处理流程。先处理监听,再处理回调。
3.一个clickable或者longClickable的View会永远消费Touch事件,enabled的会处理事件,disabled的只消耗不处理事件。
4.longClickable是在ACTION_DOWN中执行,要想执行长按事件该View必须是longClickable的,并且不能产生ACTION_MOVE;
View的clickable是在ACTION_UP中执行,想要执行点击事件的前提是消费了ACTION_DOWN和ACTION_MOVE,并且没有设置OnLongClickListener的情况下,如设置了OnLongClickListener的情况,则必须使onLongClick()返回false;
View的clickable是在ACTION_UP中执行,想要执行点击事件的前提是消费了ACTION_DOWN和ACTION_MOVE,并且没有设置OnLongClickListener的情况下,如设置了OnLongClickListener的情况,则必须使onLongClick()返回false;
5.Touch事件的分发过程中,如果消费了ACTION_DOWN,而在分发ACTION_MOVE的时候,某个ViewGroup拦截了Touch事件,则会将ACTION_CANCEL分发给该ViewGroup下面的Touch到的View,然后将Touch事件交给ViewGroup处理,并返回true;
6.针对系统事件的监听:activity中的onConfigurationChanged()处理。
最后,总结一下整个窗口管理的类图:
图6 窗口管理系统类图
参考资料:
掌握android Touch 系统
第五篇 窗口管理机制之输入机制
Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)(下)
android的窗口机制分析------事件处理
Android应用程序键盘(Keyboard)消息处理机制分析
Android Configuration change引发的问题及解决方法