前言
在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
上图为IMS事件捕获传递到APP端简易流程图,IMS 通过InputReader不断读取输入事件,WMS负责找到匹配的窗口Window,通过Socket的方式传递给对应的Window,Server端的详细流程这里不展开本篇讲APP端的事件分发,事件由InputEventReceiver接收到后接下来就是APP端事件的分发
InputEventReceiver--APP端的接收者
InputEventReceiver顾名思义为输入事件的接收者,在其对应的InputEventReceiver.cpp中有其接收事件的详细实现,该类是个抽象类,其子类为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责任链开始层层筛选事件
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 ,其实我们看下继承关系即可
由此可看出事件先分发给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分发图为
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进行分发
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训练营】