InputStage
由之前的分析,我们知道上层接收事件的入口是InputEventReceiver.dispatchInputEvent方法,但其中就一行代码,调用onInputEvent
所以对于java层的按键分发从ViewRootImpl.java的WindowInputEventReceiver中的onInputEvent开始
@Override
public void onInputEvent(InputEvent event) {
...
enqueueInputEvent(event, this, 0, true);
}
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
//这里processImmediately为true
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
void doProcessInputEvents() {
// 等待队列中其他的输入事件处理完成,调用deliverInputEvent分发这个QueuedInputEvent
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
...
deliverInputEvent(q);
}
...
}
private void deliverInputEvent(QueuedInputEvent q) {
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
这里主要调用InputStage的deliver方法进行分发,InputStage代表了输入事件的处理阶段,使用责任链模式设计模式,这些InputStage是在ViewRootImpl的setView时生成的,下一个InputStage对象持有上一个对象的引用,当自己处理不了这个事件时交由下一个处理
// 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对象未处理,调用forward方法给下一个InputStage对象处理
protected static final int FORWARD = 0;
//本InputStage对象已处理,直接finish
protected static final int FINISH_HANDLED = 1;
//消息虽然没有处理,但是要结束,调用finish方法结束
protected static final int FINISH_NOT_HANDLED = 2;
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
//分发给下一个InputStage对象来处理
forward(q);
} else if (shouldDropInputEvent(q)) {
//加FLAG_FINISHED_HANDLED标签,后面的InputStage对象不会处理
finish(q, false);
} else {
final int result;
try {
//实现类重写此方法,真正事件的分发操作是从这开始的
result = onProcess(q);
} finally {
}
//根据result做出对应处理
apply(q, result);
}
}
InputStage将输入事件的处理分成若干个阶段(Stage), 如果当前有输入法窗口,则事件处理从 NativePreIme 开始,否则从EarlyPostIme 开始,事件依次经过每个Stage,如果该事件没有被标识为 “Finished”, 该Stage就会处理它,然后返回处理结果Forward 或 Finish,Forward 运行下一个Stage继续处理,而Finished事件将会简单的Forward到下一级,直到最后一级 SyntheticInputStage。
在讲解Input事件在Activity中的分发逻辑之前,我们先来基本了解一下Activity到View的层次结构
Activity的UI结构。其实每个Activity中都包含一个Window对象,通常,Android中的Window是由PhoneWindow实现的。而PhoneWindow又将一个DecorView设置为整个窗口的根View(DecorView是一个ViewGroup)。
DecorView里面又有两个View,一个是用作title或者导航栏的,另外一个是ID为content的FrameLayout用来装我们加写的Xml文件布局的View。这也就是我们给Activity设置布局的方法命名为setContentView的原因。我们如果在AndroidStadio中创建一个Activity,setContentView,我们的加载布局的只是title下面那一块矩形区域,然后上面一般是显示我们App名称的一个title。
Key事件
其中ViewPostImeInputStage就是处理事件如何从Activity分发到View的
base/core/java/android/view/ViewRootImpl.java
final class ViewPostImeInputStage extends InputStage {
public ViewPostImeInputStage(InputStage next) {
super(next);
}
@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);
}
}
}
}
按键和触摸事件处理有一定区别,我们先看按键事件
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// 将按键传递到视图层次结构。
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
}
mView变量是在setview方法中赋值的,对于应用窗口来说, mView变量指向PhoneWindow的内部类DecorView对象。调用dispatchKeyEvent函数,按键从根节点开始自上而下分发
base/core/java/com/android/internal/policy/DecorView.java
public boolean dispatchKeyEvent(KeyEvent event) {
...
if (!mWindow.isDestroyed()) {
final Window.Callback cb = mWindow.getCallback();
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);
}
base/core/java/android/app/Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr) {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setCallback(this);
}
这里Window.setCallback函数将activty以this的方式注册进PhoneWindow中,所以cb就是activty,在PhoneWindow初始化时会生成DecorView对象,该函数中传入的mFeatureId是-1,所以此处会调用Activity的dispatchKeyEvent函数,开始在View中分发按键。因此这里继续分析Activity.dispatchKeyEvent,并且若返回为true,消费此事件。
base/core/java/android/app/Activity.java
public boolean dispatchKeyEvent(KeyEvent event) {
// PhoneWindow.superDispatchKeyEvent,实际调用的是DecorView的superDispatchKeyEvent,从DecorView开始从顶层View往子视图传递
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
//到这里如果view层次结构没有返回true,则交给KeyEvent本身的dispatch方法,Activity的onKeyDown/onKeyUp/onKeyMultiple会被触发
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
若PhoneWindow.superDispatchKeyEvent没有消费此事件,,即返回false,就会走到KeyEvent.dispatch中
base/core/java/android/view/KeyEvent.java
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
boolean res = receiver.onKeyDown(mKeyCode, this);
return res;
}
case ACTION_UP:
return receiver.onKeyUp(mKeyCode, this);
case ACTION_MULTIPLE:
...
return false;
}
return false;
}
此方法相对简单,我们先分析,这里的receiver对象其实就是Activity,Activity不仅继承了Window.Callback,还继承了KeyEvent.Callback,这里就可以通过这个接口回调到onKeyDown和onKeyUp方法
base/core/java/android/app/Activity.java
public class Activity extends ..., Window.Callback, KeyEvent.Callback,...
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
boolean res = receiver.onKeyDown(mKeyCode, this);
...
}
case ACTION_UP:
return receiver.onKeyUp(mKeyCode, this);
...
}
return false;
}
所以我们平常写APP时重写onKeyDown,onKeyUp方法就能获取到按键事件其实就是靠的这个逻辑,当然这些是建立在事件没有被提前消费这个基础上,若事件提前被PhoneWindow.superDispatchKeyEvent消费掉了,onKeyDown这些方法也就不会被触发了
base/core/java/com/android/internal/policy/PhoneWindow.java
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
这里的mDecor对象就是DecorView
base/core/java/com/android/internal/policy/DecorView.java
public boolean superDispatchKeyEvent(KeyEvent event) {
if (super.dispatchKeyEvent(event)) {
return true;
}
}
我们看到这里他又会到了DecorView中,即整个调用过程大概是这样的:
ViewPostImeInputStage.processKeyEvent–>DecorView.dispatchKeyEvent–>Activity.dispatchKeyEvent–>PhoneWindow.superDispatchKeyEvent–>DecorView.superDispatchKeyEvent
而DecorView extends FrameLayout --> extends ViewGroup
所以最终这里进入到ViewGroup的层次结构,接着会调用ViewGroup.dispatchKeyEvent方法,代码实现如下
base/core/java/android/view/ViewGroup.java
public boolean dispatchKeyEvent(KeyEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
//ViewGroup是focused并且具体的大小被设置了
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
//ViewGroup中有focused的child,且child有具体的大小
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
return false;
}
这里处理逻辑是:如果ViewGroup是focused并且具体的大小被设置了(有边界)则交给它处理,这里的super指的是父类View的方法,如果此ViewGroup中有focused的child,且child有具体的大小,则交给mFocused处理。这里可以看出如果ViewGroup满足条件,则优先处理事件而不发给子视图去处理。下面看下View的dispatchKeyEvent实现
public class View implements KeyEvent.Callback
public boolean dispatchKeyEvent(KeyEvent event) {
//调用onKeyListener
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
//调用View的onKeyUp/onKeyDown
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
return false;
}
如果View注册了OnKeyListener,并且View属于Enable状态,则触发调用OnKeyListener,然后直接返回true;如果没有,则调用view的onKeyUp/onKeyDown方法并返回true
- 分发过程中所有方法都是boolean类型函数调用,若返回
true
表示这个方法消费了这个事件,事件不继续进行传递
总结一下keyEvent从Activity->View的分发流程就是
=>Activity的dispatchKeyEvent
==>PhoneWindow的superDispatchKeyEvent
===>DecorView的superDispatchKeyEvent,DecorView的superDispatchKeyEvent再调用父类的dispatchKeyEvent
===>DecorView是窗口中的顶级视图,按键从DecorView开始往子节点分发,实际调用ViewGroup的dispatchKeyEvent
===>ViewGroup中会先判断是否可以处理KeyEvent;如果可以则调用父类(View)的dispatchKeyEvent,如果当前的ViewGroup不满足条件,则调用mFocused的dispatchKeyEvent,这里的mFocused是焦点子视图,也可以是含有焦点子视图的ViewGroup,因此这里可能会发生递归调用。
====> 在View的dispatchKeyEvent中会先调用onKey函数,即会调用各个View注册的View.OnKeyListener对象的接口
====> 在View的dispatchKeyEvent中接着调用KeyEvent的dispatch函数,因为View实现了Window.Callback函数,因此会调用View的onKeyDown/onKeyUp/onKeyMultiple函数
==>调用KeyEvent的dispatch函数,因为Activity实现了Window.Callback函数,因此会调用Activity的onKeyDown/onKeyUp/onKeyMultiple函数
=>如果整个View层次都没有返回true,则调用PhoneWindow的onKeyDown/onKeyUp函数
DecorView.dispatchKeyEvent
|----->Activity.dispatchKeyEvent
| |----->PhoneWindow.supDispatchKeyEvent
| | | |----->DecorView.superDispatchKeyEvent-->ViewGroup.dispatchKeyEvent
| | | | |----->super.dispatchKeyEvent-->View.dispatchKeyEvent
| | | | |----->ViewGroup.dispatchKeyEvent
| | | | |----->View.dispatchKeyEvent
| |----->KeyEvent.dispatch-->Activity.onKeyDown/onKeyUp
|----->PhoneWindow.onKeyDown/onKeyUp
Touch事件
Touch事件和Key事件传递的过程有点类似,最主要不一样的地方就是Key事件只要分发给focus的View就可,而Touch因携带坐标信息,需要计算坐标位置,而且在复杂嵌套布局中还要处理Touch事件传递过程中的滑动冲突问题
ViewGroup的分发事件主要是在dispatchTouchEvent中实现的,View中也有同样的方法
base/core/java/android/view/ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
// 检查mGroupFlags和onInterceptTouchEvent判断是否拦截事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false;
}
}
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
final View[] children = mChildren;
//遍历所有子布局
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//判断子布局能否获得触发事件,计算触摸点xy坐标是否在View内部
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//传入child,调用dispatchTransformedTouchEvent
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
break;
}
}
}
}
}
//传入null,表示没有或者找不到子布局
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Perform any necessary transformations and dispatch.
if (child == null) {
//child为null,说明没有子布局或者找不到符合要求的子布局,调用父类View的dispatchTouchEvent
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//找到子布局,根据子布局位置调整xy坐标,调用子布局的dispatchTouchEvent方法
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
检查拦截:
- disallowIntercept 是用来判断子view是否允许父控件拦截的,子view可以调用parent.requestDisallowInterceptTouchEvent方法,来控制该属性,并且这个属性会沿着目录树向上传递
- onInterceptTouchEvent 是用来判断ViewGroup本身是否需要拦截Touch事件,优先级低于disallowIntercept属性
//子布局调用parent.requestDisallowInterceptTouchEvent
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
//修改mGroupFlags标识
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
//自定义ViewGroup可以重写这个方法来修改Touch事件拦截逻辑
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
当然如果是自定义的ViewGroup的话,也完全可以直接重写dispatchTouchEvent整个方法达到拦截事件的目的。
说完ViewGroup,我们来看一下View的dispatchTouchEvent方法的实现
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
ListenerInfo li = mListenerInfo;
//mOnTouchListener,通过View.setOnTouchListener可以设置Touch监听
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//没有设置mOnTouchListener,通过默认onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
public boolean onTouchEvent(MotionEvent event) {
...
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
//控件不是enable,直接返回
return clickable;
}
switch (action) {
case MotionEvent.ACTION_UP:
//clickable,控件可点击的,通过View.setClickable
if (!clickable) {
break;
}
...
//clickable=true,调用performClickInternal
performClickInternal();
break;
}
return true;
}
return false;
}
private boolean performClickInternal() {
return performClick();
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
//mOnClickListener,通过View.setOnClickListener可以设置点击的监听
if (li != null && li.mOnClickListener != null) {
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
总结:
Activity:
- 首先会触发Activity的dispatchTouchEvent方法。
- 接着在dispatchTouchEvent方法中会通过Activity的root View即DecorView(id为content的FrameLayout),实质是ViewGroup,通过super.dispatchTouchEvent把touchevent派发给子view,也就是我们再Activity.onCreat方法中setContentView时设置的view。
- 若Activity下面的子view拦截了touchevent事件(返回true)则Activity.onTouchEvent方法就不会执行。
ViewGroup:
- Android事件派发是先传递到最顶级的ViewGroup,再由ViewGroup递归传递到View的。
- 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
- 子View中如果将传递的事件消费掉,ViewGroup中将结束这个事件的分发。
View:
- 触摸控件(View)首先执行dispatchTouchEvent方法。
- 若View通过setOnTouchListener,设置了监听,执行OnTouchListener.onTouch
- 如果控件监听的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调用onTouchEvent
- 如果控件不是enable的设置了onTouch方法也不会执行,否则若设置了setOnClickListener,执行OnClickListener.onClick
- 优先级:onTouchListener.onTouch > View.onTouchEvent > mOnClickListener.onClick