当你触摸屏幕时手机都干了什么?你必须知道的Android事件传递

2020-09-21 16:30:55 浏览数 (1)

前言

在Android开发中,Android的事件传递可谓比较重要,是一块比较重要的知识体系,不管是日常开发还是面试中熟悉整套的传递机制都尤为重要,Android 事件传递 其实是Android输入子系统的一部分,主要表达APP端接收并传递由IMS捕获到的输入事件的流程,例如 按键Key事件、触摸touch事件等

当你触摸屏幕时手机都干了什么当你触摸屏幕时手机都干了什么

Android输入子系统到APP端事件传递

作为一个Android研发来说,SystemServer是个很熟悉的老朋友了,老朋友SystemServer提供很多服务,例如 AMS、PMS

在Android的输入系统里主要关联到 InputManagerService(简称IMS)和WindowManagerService(简称WMS),IMS负责事件的捕获,WMS负责窗口的管理,IMS配合WMS将捕获到的事件传递到APP端的PhoneWindow

事件捕获传递到APP端(Android7.0)事件捕获传递到APP端(Android7.0)

上图为IMS事件捕获传递到APP端简易流程图,IMS 通过InputReader不断读取输入事件,WMS负责找到匹配的窗口Window,通过Socket的方式传递给对应的Window,Server端的详细流程这里不展开本篇讲APP端的事件分发,事件由InputEventReceiver接收到后接下来就是APP端事件的分发

InputEventReceiver--APP端的接收者

InputEventReceiver顾名思义为输入事件的接收者,在其对应的InputEventReceiver.cpp中有其接收事件的详细实现,该类是个抽象类,其子类为WindowInputEventReceiver

ViewRootImpl中的WindowInputEventReceiverViewRootImpl中的WindowInputEventReceiver

WindowInputEventReceiver在ViewRootImpl → setView()方法中实例化

ViewRootImpl 的setView()比较重要,他接收DecorView的实例引用,并进行各种InputStage的实例化,代码简化后如下

代码语言:java复制
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
		//全局变量设置DecorView引用
		mView = view 
		//....
            if (mInputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
			//实例化 WindowInputEventReceiver
                mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                        Looper.myLooper());
            }

		//实例化 InputStage责任调用链
            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;
        }
    }
}

什么是InputStage责任链

InputStage责任链,会将输入事件的层层筛选判断是否有自己执行还是交给下一个兄弟,其名字对应的含义为

NativePreImeInputStage

分发早于IME的InputEvent到NativeActivity中去处理, NativeActivity和普通acitivty的功能一致,不过是在native层实现,这样执行效率会更高,同时NativeActivity在游戏开发中很实用(不支持触摸事件)。

ViewPreIMEInputStage

分发早于IME的InputEvent到View框架处理,会调用view(输入焦点)的onkeyPreIme方法,同时会给View在输入法处理key事件之前先得到消息并优先处理,View系列控件可以直接复写onKeyPreIme( 不支持触摸事件)。

ImeInputStage

分发InputEvent到IME处理调用ImeInputStage的onProcess,InputMethodManager的dispatchInputEvent方法处理消息(不支持触摸事件)。

EarlyPostImeInputStage

与touchmode相关,比如你的手机有方向键,按方向键会退出touchmode,这个事件被消费,有可能会有view的背景变化,但不确定(支持触摸事件)。

NativePostImeInputStage

分发InputEvent事件到NativeActivity,IME处理完消息后能先于普通Activity处理消息(此时支持触摸事件)。

ViewPostImeInputStage

分发InputEvent事件到View框架,view的事件分发(支持触摸事件)。最终会调用到输入焦点的3个方法:使用setKeyListener注册的监听器的onKey,之后是onKeyDown和onKeyUp,或者调用activity的onKeyDown和onKeyUp方法,也就是兜底处理无人处理的key事件

SyntheticInputStage

未处理 InputEvent最后处理。

WindowInputEventReceiver与InputStage责任链的传递

WindowInputEventReceiver覆盖了InputEventReceiver的onInputEvent方法 并调用了 ViewRootImpl # enqueueInputEvent 方法

代码语言:java复制
    final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        @Override
        public void onInputEvent(InputEvent event, int displayId) {
            enqueueInputEvent(event, this, 0, true);
        }
        //.........
      
    }

enqueueInputEvent 方法 最终会调用 ViewRootImpl # deliverInputEvent

代码语言:java复制
 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 {
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        if (q.mEvent instanceof KeyEvent) {
            mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
        }

        if (stage != null) {
            handleWindowFocusChanged();
            stage.deliver(q);//责任链开始传递
        } else {
            finishInputEvent(q);
        }
    }

经过ViewRootImpl内部一顿调用后,接下来由InputStage责任链开始层层筛选事件

InputStage责任链InputStage责任链

ViewPostImeInputStage

InputStage责任链中ViewPostImeInputStage负责分发InputEvent事件到View框架,其他的我们暂不关注

代码语言:javascript复制
  //其实现也在RootViewImpl 中
    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);
                }
            }
        }
        //触摸事件也是指针事件 包括鼠标输入也是指针事件 可以看到最终mView.dispatchPointerEvent进行了分发
           private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
            
            //这里的mView 为DecorView 上面SetView中讲到
            //可以看到最终DecorView.dispatchPointerEvent进行了分发
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false;
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            //返回操作标识 是处理完了 还是交给下一个InputStage
            return handled ? FINISH_HANDLED : FORWARD;
        }

可以看到最终mView.dispatchPointerEvent进行了分发,mView 即在setView时传入的DecorView,至此即将来到View层分发逻辑

为什么说即将呢,我们看DecorView 的分发逻辑

DecorView的事件传递分发

DecorView继承于FrameLayout,固也继承于View,dispatchPointerEvent在View及其子类中没有重写操作

dispatchPointerEvent在View中的实现为

代码语言:javascript复制
 public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);//分发触摸事件
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

可以看到如果是Touch事件则走dispatchTouchEvent事件,下面是DecorView的dispatchTouchEvent分发逻辑

代码语言:javascript复制
@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);
}

这里的WindowCallback 指的是谁呢,没错 此乃Activity 或者Dialog ,其实我们看下继承关系即可

Window.Callback的继承关系图Window.Callback的继承关系图

由此可看出事件先分发给WindowCallBack的实现类,如Activity 或者Dialog等,

以Activity为例,我们来看Activity的实现

Activity内dispatchTouchEvent实现

代码语言:javascript复制
//Activity 
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

PhoneWindow

代码语言:javascript复制
//PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

DecorView

decorView 为FrameLayout子类固这里调用的为ViewGroup的dispatchTouchEvent

代码语言:javascript复制
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

此时可以得出decorView分发图为

decorView分发图decorView分发图

ViewGroup事件分发

先来看段dispatchTouchEvent的代码

代码语言:javascript复制
@Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
		//.....
		//是否拦截
          intercepted = onInterceptTouchEvent(ev);
           if (!canceled && !intercepted) { //判断1
              final View[] children = mChildren;
              for (int i = childrenCount - 1; i >= 0; i--) {
				//.....
				//判断touch所在Child
                  if (!canViewReceivePointerEvents(child)
                          || !isTransformedTouchPointInView(x, y, child, null)) {
                      ev.setTargetAccessibilityFocus(false);
                      continue;
                  }
             }
				//分发 1 
                  if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                     //....
                  }
			}
			//如果上面没有子View进行分发 那么 mFirstTouchTarget 为null 
			if (mFirstTouchTarget == null) { //判断2
                // No touch targets so treat this as an ordinary view.
                //分发 2
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
			//....		
 }
 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        // 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);
        }
        return handled;
    }

首先看是否拦截 如果拦截 则 intercepted 为true 判断1 不会进入 , mFirstTouchTarget 为null 会进入判断2

判断2 内调用 分发2 dispatchTransformedTouchEvent 参数view传入的null ,走 child ==null 的分支逻辑,最终调用super.dispatchTouchEvent 即父类View的分发逻辑也就是自己处理

如果不拦截则 进入 判断1 通过循环子View 找到 触摸点 在那个子View内,然后调用dispatchTransformedTouchEvent

传入触摸点所命中View,走child!=null的分支逻辑 即调用child.dispatchTouchEvent 进行分发 继续由子view进行分发

ViewGroup事件分发ViewGroup事件分发

View事件分发

代码语言:javascript复制
  public boolean dispatchTouchEvent(MotionEvent event) {
	   //如果在可滑动组件的前套内 则停止滚动
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
			//如果有 OnTouchListener 先走 OnTouchListener的onTouch
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
			//如果result为false 走onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
		//.....
        return result;
    }

首先判断是否在可滑动组件内如果是则停止滑动,后查看是否有 OnTouchListener有的话先走OnTouchListener

如果经过前面处理后result依然为false则走 onTouchEvent

代码语言:javascript复制
简版 onTouchEvent
     public boolean onTouchEvent(MotionEvent event) {
		//是不是一个可以点击的 能接收事件的
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                     if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
						//取消长按检测
                         removeLongPressCallback();
                             if (!post(mPerformClick)) {
							//调用OnClickListener
                                 performClickInternal();
                     }
					//......
                    break;
                case MotionEvent.ACTION_DOWN:
					// 发送一个检查是否是长按的Runnable  postDelayed(mPendingCheckForLongPress
					//检测时间默认为 DEFAULT_LONG_PRESS_TIMEOUT = 500; 500毫秒
                    break;
				case  ACTION_MOVE:
				如果x,y ponit移出了当前view 
				removeTapCallback();
				removeLongPressCallback();
				case ACTION_CANCEL:
    }

onTouch内进行了 ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL相关处理

这里简版给出来长按及点击的逻辑,在ACTION_DOWN时会发送一个500毫秒的 长按Runnable 检测,如果到了500毫秒,还没有接收到ACTION_UP事件则认为是一个长按点击,如果在500毫秒内接收到了ACTION_UP则认为是一个点击事件 走OnClick

总结

Android事件由IMS负责捕获,例如触摸、按键、鼠标等事件,捕获到事件后WMS配合判断当前应该分发给哪个Window

SystemServer与APP端的事件传递通过Socket通讯,APP端由InputEventReceiver的子类,WindowInputEventReceiver 接收事件并传递给InputStage责任链,InputStage责任链进行层层判断是否消费还是交个下一链处理,其中 ViewPostImeInputStage 负责分发InputEvent事件到View框架,View层由DecorView首先接收到事件,DecorView内分发给Window.callBack其实现类有Activity dialog等。

在Activity内部分发又调用PhoneWindow的superDispatchTouchEvent,最终又调用回DecorView的superDispatchTouchEvent,

DecorView为FrameLayout的子类固调用ViewGroup的DispatchTouchEvent。ViewGroup 先判断是否要拦截如果拦截自己处理,如果不拦截则子View负责进行后续分发。

【技术创作101训练营】

0 人点赞