Android触摸事件机制

触摸事件机制是Android触摸屏触控操作的关键。

触摸事件分发分为服务端和应用端。在Server端由WindowManagerService(WMS,窗口管理服务)负责管理。在Client端则是由ViewRootImpl(负责控制View树的UI绘制和事件消息的分发)负责分发的。

Touch事件从WMS经过一系列方法到ViewRootImpl进行传递,当Touch事件传递到了ViewRootImpl中后,就会在Activity的View树中进行分发。

WMS至ViewRootImpl

上面的图是网上找的,Android4.4后,InputManager变成了InputManagerService,ViewRoot变成了ViewRootImpl。

大致流程就是:WMS在启动之后,会在native层启动两个线程:InputReaderThread和InputDispatchThread,前者用来读取输入事件,后者用来分发输入事件,输入事件经过nativie层的层层传递,最终会传递到java层的ViewRootImpl中。

Touch事件从server端传递到client端采用的IPC方式并不是Binder,而是共享内存和管道,至于为什么不采用Binder,应该是共享内存的效率更高,而管道(注意,两个管道,分别负责不同方向的读和写)只负责通知是否有事件发生,传递的只是一个很简单的字符串,因此并不会太多地影响到IPC的效率。

ViewRootImpl至Activity

ViewRootImpl -> DecorView -> PhoneWindow -> Activity

ViewRootImpl事件分发

事件传递到ViewRootImpl后,会调用dispatchInputEvent方法。

1
2
3
4
5
6
7
8
9
public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = event;
args.arg2 = receiver;
// 发送MSG_DISPATCH_INPUT_EVENT事件
Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
  • InputEvent:输入事件的基类,它有两子类:KeyEvent(键盘输入事件),MotionEvent(屏幕触摸事件)。
  • InputEventReceiver:为应用程序提供一个接收者来接收输入事件。也就是用来接收输入事件->交给ViewRootImpl的dispatchInputEvent去分发。

接下来会发送MSG_DISPATCH_INPUT_EVENT事件到ViewRootHandler,ViewRootHandler在UI线程中。

1
2
3
4
5
6
7
case MSG_DISPATCH_INPUT_EVENT: {
SomeArgs args = (SomeArgs)msg.obj;
InputEvent event = (InputEvent)args.arg1;
InputEventReceiver receiver = (InputEventReceiver)args.arg2;
enqueueInputEvent(event, receiver, 0, true);
args.recycle();
} break;

接下来看看enqueueInputEvent方法。输入事件会以队列方式按顺序等待执行。

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
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
// 将当前输入事件加入队列中排列等候执行
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// 输入事件添加进队列后,加入输入事件的默认尾部
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);

// 事件是否是同步事件
if (processImmediately) {
// UI线程则为同步
doProcessInputEvents();
} else {
// 非UI线程则为异步
scheduleProcessInputEvents();
}
}

异步情况下,MSG_PROCESS_INPUT_EVENTS事件发送到ViewRootHandler,最终也是调用了doProcessInputEvents方法。

1
2
3
4
5
6
7
8
9
10
11
12
private void scheduleProcessInputEvents() {
if (!mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = true;
Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
}

case MSG_PROCESS_INPUT_EVENTS:
mProcessInputEventsScheduled = false;
doProcessInputEvents();

接下来看看doProcessInputEvents方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void doProcessInputEvents() {
while (mPendingInputEventHead != null) {
... 循环取出队列中的输入事件

// 事件分发处理
deliverInputEvent(q);
}

// 处理完所有输入事件,清除标志
if (mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = false;
mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void deliverInputEvent(QueuedInputEvent q) {
...

// 初始化InputStage
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}

if (stage != null) {
// 交付事件阶段
stage.deliver(q);
} else {
// 结束分发
finishInputEvent(q);
}
}

这里InputStage是一个基类,表示事件处理的阶段。当一个InputEvent到来时,ViewRootImpl会寻找合适它的InputStage来处理,每个InputEvent对应一个InputStage。InputStage也采用了类似责任链的模式处理,InputStage主要有以下几种状态:

  • deliver:交付事件阶段处理。
  • forward:处理下个阶段。
  • finish:标志当前输入事件处理完毕。
  • apply:根据onProcess返回的RESULT_CODE判断当前阶段。
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
apply(q, onProcess(q));
}
}

protected void finish(QueuedInputEvent q, boolean handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
if (handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
}
forward(q);
}

protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}

protected void onDeliverToNext(QueuedInputEvent q) {
if (DEBUG_INPUT_STAGES) {
Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
}
if (mNext != null) {
mNext.deliver(q);
} else {
finishInputEvent(q);
}
}

protected void apply(QueuedInputEvent q, int result) {
if (result == FORWARD) {
forward(q);
} else if (result == FINISH_HANDLED) {
finish(q, true);
} else if (result == FINISH_NOT_HANDLED) {
finish(q, false);
} else {
throw new IllegalArgumentException("Invalid result: " + result);
}
}

最终的事件分发处理则是在apply方法里的onProcess方法。

对于点击事件来说,InputState的子类ViewPostImeInputStage可以处理它。

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
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}

private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;

...

boolean handled = mView.dispatchPointerEvent(event);

...

return handled ? FINISH_HANDLED : FORWARD;
}

此处mView即为DecorView,dispatchPointerEvent方法在View中:

1
2
3
4
5
6
7
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}

DecorView重写了dispatchTouchEvent:

1
2
3
4
5
6
Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

如果有Callback则用Callback的dispatchTouchEvent(ev)否则直接使用super.dispatchTouchEvent(ev),而此Window.CallBack由Activity实现。

1
2
public class Activity extends ContextThemeWrapper implements  Window.Callback {
}

Activity至View

Activity -> PhoneWindow -> DecorView -> ViewGroup -> View

Activtiy分发流程

当触摸事件传递到Activity,Activity的dispatchTouchEvent()就会被调用:

1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

如果当前事件是DOWN事件,调用了onUserInteraction方法,该方法是一个空方法,我们可以重载该方法,在DOWN事件做一些处理。接着就把事件传递给Window来处理该事件。如果返回true,表示有View处理该事件,onTouchEvent方法返回了true,整个事件处理完成。否则Activity的onTouchEvent方法就会被调用。

Window分发流程

Window类是abstract的,唯一的具体实现类是PhoneWindow类,PhoneWindow的superDispatchTouchEvent:

1
2
3
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}

即调用了DecorView的superDispatchTouchEvent方法。

DecorView分发流程

1
2
3
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}

DecorView继承于FrameLayout,FrameLayout继承于ViewGroup。因此就是调用了ViewGroup的dispatchTouchEvent方法。

DecorView就是我们的顶层View,当我们通过setContentView方法设置的是顶层View的一个id为content的子View。

此后,事件在自定义的View树中进行分发。

触摸事件

先简述一下View的触摸事件,主要包含以下三个方法:

  • dispatchTouchEvent:用来进行事件的分发,如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和View的dispatchTouchEvent方法的影响,表示是否消费当前事件;

  • onInterceptTouchEvent:用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件;

  • onTouchEvent:在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消费当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

View树事件分发流程

View树事件分发使用了责任链方式处理,具体流程简略如下(截取自《Android开发艺术探索》):

1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev) { 
boolean consume = false;
if(onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
  1. 事件由父View传递给子View,ViewGroup可以通过onInterceptTouchEvent()方法对事件拦截,停止其向子view传递。默认ViewGroup不对事件进行拦截。

  2. 如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。

  3. 如果当前View拦截事件,则自己进行onTouchEven处理,否则交由子View的dispatchTouchEvent处理,子View递归此过程。一旦触摸事件被消费,则后续事件序列不再进行调用。

  4. ViewGroup和View的区别在于,View没有onInterceptTouchEvent事件,无法对事件直接进行拦截。但是子View可通过parentView.requestDisallowInterceptTouchEvent()对父视图调用表明父View不应使用onInterceptTouchEvent()截获触摸事件。

触摸事件的MotionEvent

View触摸事件的MotionEvent主要包括了ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_CANCEL,ViewGroup还包含了POINTER_UP、POINTER_DOWN等事件。

  1. 同一个事件序列指的是从手指触摸屏幕的那一刻开始,到手指离开屏幕的那一刻结束,在这个过程产生的一系列事件。以ACTION_DOWN事件开始,可能经过多个ACTION_MOVE事件,最终以ACTION_UP事件结束。

  2. 某个View一旦开始处理事件,如果不消费掉ACTION_DOWN,那个这个事件系列的其他事件也不会再交与他处理,而是传给父容器的onTouchEvent()进行处理。

  3. 如果View不消费除ACTION_DOWN以外的事件,父容器的onTouchEvent()并不会被调用,这个View也可以继续收到其他事件,然后重新传递给了Activity进行处理。

onTouch()、onClick()、onTouchEvent()关系

  • onTouch:OnTouchListener回调方法,监听触摸事件处理。
  • onClick:OnClickListener回调方法,监听点击事件处理。
  • onTouchEvent:处理触摸事件。

调用顺序:onTouch在View的dispatchTouchEvent中先调用,onTouchEvent后调用,onClick在onTouchEvent的ACTION_UP的performClick方法中调用。

如果onTouch返回true, onTouchEvent就不会调用,参考View的dispatchTouchEvent的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean dispatchTouchEvent(MotionEvent event) {
...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}
...

clickable、enable关系

  • clickable:表示控件是可点击的。
  • enable:表示控件是可用的。

setClickable和setEnabled都是对mPrivateFlags做操作,判断View是否可点击以及是否可用也是基于mPrivateFlags做判断。

当mPrivateFlags & CLICKABLE == CLICKABLE时代表控件是可点击的。

当mViewFlags & ENABLED_MASK == ENABLED代表控件是可用的。

在View中对点击状态的判断主要是在onTouchEvent方法中,而对可用状态的判断则分布在 onTouchEvent和dispatchTouchEvent中。

  1. 当View是不可用的时候,通过setOnTouchListener设置的OnTouchListener中的onTouch方法将不会执行。

  2. 当View是不可用的时候,onTouchEvent会被执行,但不会执行实质的逻辑,比如onClick、onLongClick等方法不会被执行到。此时onTouchEvent的返回值由该View能不能点击(包括长按和短按等点击状态)来决定。可以点击时返回true,否则返回false。

  3. 当View是不可点击的时候,除非调用过View的setTouchDelegate方法传入 mTouchDelegate,否则onTouchEvent必定会返回false,具体的逻辑,例如onClick、onLongClick等不会被调用。

android:focusable和android:focusInTouchMode区别

  • focusable:针对在键盘下操作的情况,比如非触屏手机或者TV,如果设置为true,则键盘上下左右选中,焦点会随之移动。
  • focusableInTouchMode:针对触屏情况下的,也就是我们点击屏幕的上的某个控件时,不要立即执行相应的点击逻辑,而是先显示焦点(即控件被选中),再点击才执行逻辑。
  1. android:focusable=“true”不会改变android:focusableInTouchMode,因此只在键盘状态下显示焦点,在TouchMode状态下,依旧无法显示焦点。

  2. android:focusable=“false”,一定会使android:focusableInTouchMode=“false”。

0%