在Android的View系统中,TouchEvent消息的传递是非常重要的,只有深入TouchEvent消息的传递机制才能更好的理解一些View组件的原理和编写自定义View控件。本文基于Android 5.1源码进行分析。

Android系统的消息处理过程,大致可以如下

触摸消息 ---> 消息处理前端 ---> 窗口管理系统 ---> 应用窗口

当用户通过触摸屏等输入设备产生触摸信息,该消息会首先被消息处理前端转换为更明确的消息,比如DOWN/UP消息。然后通过窗口管理系统(WindowManagerService)根据消息的位置坐标去匹配所有的窗口,判断该坐标的落地窗口的哪个区域中,则把该消息下发给相应的窗口。最后窗口把该消息下发给根View,如果根View没有消费该消息则下发给子View,这样层层传递下去。

从源码开始分析触摸消息的传递机制

Let’s reading the fucking souce code.

首先从Activity开始分析,看WindowManagerService是如何将消息分发给应用层的。

  • Activity它有两个关键点

    1. Window mWindow成员变量,Window是一个顶级窗口抽象类,它的实现类是PhoneWindow,在Activity的attach()函数中通过mWindow = PolicyManager.makeNewWindow(this)获得,PhoneWindow有一成员变量DecorView mDecor

    2. 实现了Window.Callback接口,它包含了若干事件回调函数,其中就有dispatchTouchEvent(MotionEvent event)函数,并在Activity的attach()函数中通过mWindow.setCallback(this)注册了回调

  • DecorView它继承于FrameLayout,它是Activity的顶级Layout,通过setContentView()加入到Activity的View其实都是被加入到该Layout中,它负责了触摸消息的向下分发

  • ViewRootImpl在Android3.0以前叫ViewRoot,它不是一个View,它是用来沟通WindowManagerService,它想wms中注册了消息接收通道(InputChannel)处理来自底层的事件消息。这些都在函数ViewRoot.setView中完成

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* ViewRootImpl.java
*/
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
/**
* We have one child
*/
public void setView (View view, WindowManager.LayoutParams attrs, View panelParentView){
// ......
mView = view;
// Schedule the first layout -before- adding to the window manager, to make sure we do the relayout before receiving any other events from the system.
requestLayout();
if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
// ......
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
}
}

setView()方法首先将对mView赋值,然后调用requestLayout()函数来通知InputManager,这个Activity窗口是当前被激活的窗口,最后当窗口属性没有INPUT_FEATURE_NO_INPUT_CHANNEL标志时创建mInputChannel并通过mWindowSession.addToDisplay()函数注册通道。

在RootViewImpl中的函数通道是各种策略(InputStage)的组合,各策略负责的任务不同,如SyntheticInputStage、ViewPostImeInputStage、NativePostImeInputStage等待,这些策略以链表结构结构起来,当一个策略者没有消费事件时,就传递个下一个策略者。其中触摸事件由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
27
28
29
30
31
32
33
34
35
36
37
final class ViewPostImeInputStage extends InputStage {
//......
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
// If delivering a new non-key event, make sure the window is
// now allowed to start updating.
handleDispatchDoneAnimating();
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;
mAttachInfo.mUnbufferedDispatchRequested = false;
boolean handled = mView.dispatchPointerEvent(event);
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
}

onProcess()函数中如果事件不是按键事件且是触摸点事件则调用processPointerEvent()函数,在该函数中调用mView.dispatchPointerEvent()函数来下发该事件

DecorView是PhontWindow的内部类,在DecorView的dispatchTouchEvent()的函数实现中可以看到它直接调用了callback的回调函数,而这个函数的实现就是Activity了

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
public class PhoneWindow extends Window implements MenuBuilder.Callback {
/**
* Set the Callback interface for this window, used to intercept key
* events and other dynamic operations in the window.
*
* @param callback The desired Callback interface.
*/
public void setCallback(Callback callback) {
mCallback = callback;
}
/**
* Return the current Callback interface for this window.
*/
public final Callback getCallback() {
return mCallback;
}
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
// ......
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
}
}

通过对RootViewImpl的分析,我们知道了触摸事件的下发关键在与mView这个对象,那mView这个对象是在何时被赋值的呢?

它在ActivityThread.handleResumeActivity()函数中被赋值的,首先获得Window对象r.window = r.activity.getWindow(),然后再通过该Windowd对象的r.window.getDecorView()函数 获得顶级View,最后通过WindowManager的实例对象的wm.addView(decor, l)函数将View添加到窗口中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* ActivityThread.java
*/
public final class ActivityThread {
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// ......
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
// ......
}
}
}

再来看WindowManager实例中的addView()函数,它直接调用了WindowManagerGlobal的addView()函数,从这能看出,WindowManagerGlobal才真正是WindowManager的实现类,WindowManagerImpl只不过代理了一下

1
2
3
4
5
6
7
8
9
10
11
12
/**
* WindowManagerImpl.java
*/
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
//......
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
//......
}

最后,WindowManagerGlobal的addView()函数中直接new了一个ViewRootImpl对象,然后在这个对象上直接调用了ViewRootImpl的setView()函数,就这样把Activity的顶级View赋值给了它的成员变量

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
/**
* WindowManagerGlobal.java
*/
public final class WindowManagerGlobal {
//......
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
View panelParentView = null;
// ......
root = new ViewRootImpl(view.getContext(), display);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
}

至此,已经完全分析了Activity是怎么通过ViewRootImpl这个对象来接收底层的事件消息,下一节中,我会对从Activity出发,进一步分析事件的传递机制。

本人才疏学浅,有分析错误的地方,还请大家纠正。另外,本文中,有些逻辑分析是来自老罗的blog,非常感谢老罗,他写的Android文章十分有用,大家也不妨去看看。

Android应用程序键盘(Keyboard)消息处理机制分析

Android应用程序窗口设计框架介绍

原创不易,欢迎转载,但还请注明出处:waynell.github.io