输入事件派发第二篇!
下图便是"View hierarchy"的树结构,ViewRootImpl居于"View hierarchy"的顶层,但是它并非View的一分子,可以把它理解为管理者,核心任务是和WindowManagerService进行通信。
Activity resume的时候addView@WindowManagerGlobal方法会调用setView@ViewRootImpl方法,部分代码如下:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
......
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
// 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(); //创建InputChannel对象
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//添加窗口
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
} catch (RemoteException e) {
......
} finally {
......
}
......
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
view.assignParent(this);
mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
......
// 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;
}
}
}
上面的代码只保留了三部分:
创建的mInputChannel对象会作为参数传入" mWindowSession.addToDisplay(…)“的调用之中,” mWindowSession.addToDisplay(…)"会调用addWindow@WindowManagerService方法,在这个方法中openInputChannel@WindowState方法会被调用,经此方法传入的mInputChannel对象也就和Native层的InputChannel有了联系,这是上篇InputChannel中的内容。
WindowInputEventReceiver继承了InputEventReceiver,InputEventReceiver的初始化,请参考上篇,这里就不再说了。构建WindowInputEventReceiver对象时使用了mInputChannel作为参数,当有input event需要派发时,NativeInputEventReceiver会调用dispatchInputEvent@WindowInputEventReceiver方法,后者会调用onInputEvent@WindowInputEventReceiver方法,这样input event就传入了ViewRootImpl中,这部分我们先说到这里,稍后再讲ViewRootImple对input event的处理。
ViewRootImpl的setView方法设置了input event处理的pipeline, 如下:
// 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;
InputStage可以理解为用来处理input event的"阶段"。上面代码中的"ViewPostImeInputStage"和"ViewPreImeInputStage"等都是iInputStage的子类。通过这些类的构造方法,我们可以发现创建这些子类对象时传入的参数,其实是"next stage":
public InputStage(InputStage next) {
mNext = next;
}
也就是说越是后面创建的,优先级就越高,所以根据代码中子类stage对象的创建顺序,在处理input event时的优先级顺序如下(从高到低):
stage实例 | 类型 | commetns |
---|---|---|
nativePreImeStage | NativePreImeInputStage | 将input event派发给native activity处理,不支持pointer event |
viewPr中eImeStage | NativePreImeInputStage | 将input even派发给view hierarchy处理,不至此pointer event |
imeStage | ImeInputStage | 将input event派发给ime(输入法引擎)处理 |
earlyPostImeStage | EarlyPostImeInputStage | 对post-ime的input event做早期处理 |
nativePostImeStage | NativePostImeInputStage | 将post-ime的input event派发给native activity处理 |
viewPostImeStage | ViewPostImeInputStage | 将post-ime的input event派发给view hierarchy处理 |
mSyntheticInputStage | SyntheticInputStage | 处理unhandled event中的合成event,都是些和track ball,joy stack等相关的event |
这些stage分成三类:
mFirstInputStage 变量的值是nativePreImeStage,如果从mFirstInputStage开始处理,是一个"正常"的处理顺序。
mFirstPostImeInputStage变量的值是earlyPostImeStage,跳过了"输入法之前的stage"和"输入法stage"。
ViewRootImpl对input event的处理,上面我们说到input event通过WindowInputEventReceiver的onInputEvent方法派发到了ViewRootImpl中,下面是此方法的代码:
@Override
public void onInputEvent(InputEvent event, int displayId) {
enqueueInputEvent(event, this, 0, true);
}
onInputEvent@WindowInputEventReceiver方法调用了ViewRootImpl的enqueueInputEvent方法,后者用InputEvent类型的event为参数构造QueuedInputEvent类型的对象,并将新对象放入队列尾部,然后调用doProcessInputEvents@ViewRootImpl方法从队列头部取出QueuedInputEvent,交给deliverInputEvent@ViewRootImpl方法进行处理,后者会调用合适stage的deliver方法,代码如下:
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
//如果event要跳过输入法,那么就使用mFirstPostImeInputStage,从EarlyPostImeInputStage开始处理;否则使用mFirstInputStage,从NativePreImeInputStage开始处理。
//拥有FLAG_DELIVER_POST_IME的QueuedInputEvent才需要跳过输入法,通过dispatchKeyFromIme@ViewRootImpl发过来的event才会有这个flag,InputMethodManager.java内会调用这个方法。
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (q.mEvent instanceof KeyEvent) {
mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
}
if (stage != null) {//如果stage不为空,那么就调用stage的deliver方法
handleWindowFocusChanged();
stage.deliver(q);
} else {//否则,就结束派发
finishInputEvent(q);
}
}
拥有FLAG_DELIVER_POST_IME的QueuedInputEvent才需要跳过输入法,通过dispatchKeyFromIme@ViewRootImpl发过来的event才会有这个flag,InputMethodManager.java内会调用这个方法。大部分的event是从mFirstInputStage,即NativePreImeInputStage开始处理的。
InputStage的deliver方法如下:
/**
* Delivers an event to be processed.
*/
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
//如果q.mFlags已经含有FLAG_FINISHED,说明已经被处理过了,直接调用forward,起诉forward会转交到下一个阶段
forward(q);
} else if (shouldDropInputEvent(q)) {
//如果需要丢弃就调用finish方法处理
finish(q, false);
} else {
//apply方法调用了onProcess方法,各个子类都重写了这个方法; apply方法用来做最终处理,调用finish或者forward方法
apply(q, onProcess(q));
}
}
deliver方法只是负责根据event的状态调用合适的处理方法,各个子类也没有重写这个方法。
各个stage是在他们的onProcess方法内消耗event。
上面我们将stage分成了三类:“pre-ime”, “ime"和"post-ime”; 其实也可以从派发对象分类成:"native"和"view hierarchy"等,下面我们就看看"View hierarchy"对input event的派发。
对于key event,"pre-ime stage”是有拦截的,下面是ViewPreImeInputStage相关代码:
final class ViewPreImeInputStage extends InputStage {
public ViewPreImeInputStage(InputStage next) {
super(next);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
}
return FORWARD;
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mView.dispatchKeyEventPreIme(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
}
代码比较简单,processKeyEvent会调用"mView.dispatchKeyEventPreIme(event)"进行处理,这里的"mView"是DecorView。dispatchKeyEventPreIme依然是ViewGroup中的方法:
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
return super.dispatchKeyEventPreIme(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
return mFocused.dispatchKeyEventPreIme(event);
}
return false;
}
满足if的条件后调用"super.dispatchKeyEventPreIme(event)", 这个"super"是View.java,默认实现最终是直接返回false。如果满足"else if"后会调用当前焦点View的dispatchKeyEventPreIme方法,如果焦点View重新了该方法就可以消耗这个input event。
我们直接跳过ime stage,看看post-ime stage对key event的处理。
ViewPostImeInputStage的onProcess方法如下:
@Override
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);
}
}
}
如果是KeyEvent,那么会调用processKeyEvent方法处理,此方法会调用DecorView的dispatchKeyEvent方法,下面看看这个方法:
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
......
//省略shotcut相关code
if (!mWindow.isDestroyed()) {
final Window.Callback cb = mWindow.getCallback();//mWindow是PhoneWindow对象引用,而callback是Activity
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
此方法对Key event的派发顺序如下:
上面只是说了ViewPostImeInputStage的部分代码,细节部分还是要看代码。
对于来自屏幕(SOURCE_TOUCHSCREEN)的Touch event, “pre-ime stage"是不会做处理的,所以直接看"post-ime stage”,依然还是上面提到的ViewPostImeInputStage,onProcess方法的代码如下:
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
//触摸事件的source是SOURCE_TOUCHSCREEN,
//而SOURCE_TOUCHSCREEN = 0x00001000 | SOURCE_CLASS_POINTER
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);
}
}
}
对于touch event,ViewPostImeInputStage会调用processPointerEvent方法进行处理,后者会调用DecorView的dispatchPointerEvent方法,DecorView间接继承了View但并没有重写dispatchPointerEvent方法,所以仍然是View中的实现:
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
这个方法调用了dispatchTouchEvent方法,DecorView重写了这个方法,代码如下:
@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);
}
下图是调用进入dispatchTouchEvent@DecorView后的简单处理流程:
如果没有"Callback", 那么会调用DecorView间接父类ViewGroup中的dispatchTouchEvent方法。有”Callback“的情况会比较复杂一些,以"Callback"为Activity为例; Activity可以重写dispatchTouchEvent方法,按照需求对Touch event进行处理; 图中是Activity.java源码中的实现,代码如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
此方法调用PhoneWindow的superDispatchTouchEvent方法,后者会调用DecorView的superDispatchTouchEvent方法,最终调用ViewGroup的dispatchTouchEvent方法进行处理,如果没有消耗掉事件,那么再由Activity(onTouchEvent方法)进行处理。
在上面的派发过程中,我们发现DecorView的dispatchTouchEvent方法会被调用(如果Callback没有拦截)。dispatchTouchEvent方法的定义在View.java中,ViewGroup重写了这个方法,添加了自己的处理逻辑; ViewGroup的子类也可以按照自己的需求进行重写,但是DecorView作为顶层的ViewGroup,并没有重写这个方法,正常情况下它会将Event交给子view进行处理,当然对于没有重写该方法的ViewGroup也是一样的。
dispatchTouchEvent方法会调用onInterceptTouchEvent和onTouchEvent方法,这三个方法在整个View体系的派发过程中扮演着重要的角色:
ViewGroup的dispatchTouchEvent方法的代码挺多, 就不贴了,画了一个简单的流程图:
从手指触摸屏幕,到手指离开屏幕,这算一个事件序列; 这个事件序列从down事件开始,up事件结束,中间有数量不定的move事件。
mFirstTouchTarget可以理解为单链表,dispatchTouchEvent每次在收到down事件时都会把这个单链表清空,只有当子View消耗事件后,这个链表才会追加新的Touch target。我们看看这块代码,
也只有满足三个条件中的一个才会有机会追加链表, 所以对于down–move–up这样的事件序列,mFirstTouchTarget最多也只有一个节点。
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
....
}
下面的流程图是View.java中dispatchTouchEvent方法的逻辑:
View没有子view,所以会自己处理event。如果View中有"OnTouchListener",那么Listener的onTouch方法会先被调用; 如果没有消耗掉event,那么View的onTouch方法会被调用; 如果是"ACTION_UP",而且有"OnClickListener", 如果View的onTouch方法没有消耗event,那么Listener的onClick方法会被调用。
在上面的图中,DecorView中的布局包含了一个ViewGroup: "view-group"和一个View:“view-1”, "view-group"显示顺序比View靠前(Z值大); "view-group"中包含了两个View:“view-2"和"view-3”,“view-2"的显示顺序要比"view-3"靠前。
当有input touch event时,dispatchTouchEvent@DecorView方法会先将event派发给"view-group”,“view-group"的dispatchTouchEvent方法会优先调用"view-2"的dispatchTouchEvent方法进行处理(onTouchEvent会被调用);
如果"view-2"没有消耗event,那么"view-group"会将event派发给"view-3"处理;
如果"view-3"也没有消耗event,那么"view-group"的dispatchTouchEvent和onTouchEvent方法会被调用;
如果"view-group"仍然没有被消耗掉event,那么DecorView就会将event派发给"view-1”;
如果"view-1"没有消耗掉event,那么DecorView会调用自己间接父类View的dispatchTouchEvent方法进行处理,这样自己的onTouchEvent会被调用。