Android中的事件分发机制

微信扫一扫,分享到朋友圈

Android中的事件分发机制

Android 是基于“事件驱动”模型的。所谓事件驱动,简单地说就是你点什么按钮(即产生什么事件),系统执行什么操作(即调用什么函数)。当然事件不仅限于用户的操作,事件驱动的核心自然是事件。

事件的分类

从事件角度说,事件驱动程序的基本结构是由一个事件收集器、一个事件发送器和一个事件处理器组成。事件收集器专门负责收集所有事件,包括来自用户的(如鼠标、键盘事件等)、来自硬件的(如时钟事件等)和来自软件的(如操作系统、应用程序本身等)。

从广义上来说,事件的发生源可以分为 软件
硬件
两类,这里侧重对 硬件
部分的讨论。

事件是由真实物理硬件产生的消息,表明设备使用者的某种意愿。

如果从硬件设备角度为事件分类,最重要的有以下几种分类

  • 按键事件 (KeyEvent)

    由物理按键产生的事件。

  • 触摸事件 (TouchEvent)

    在触摸屏上的点击、滑动等事件,是 Android 系统中使用最广泛的事件。

  • 鼠标事件 (MouseEvent)

    鼠标操作引起的事件,如单击、双击、移动等。

InputEvent

Android 针对上面提到的事件,提取出了一个统一的抽象接口,这就是 InputEvent

InputEvent 下面有两个类:KeyEvent 和 MotionEvent。KeyEvent 用于表达按键事件,MotionEvent 则是将所以能产生 MoveEvent 的事件源进行统一管理。

事件的投递流程

事件源产生后,Linux 内核收集到原始信息,Android 系统通过 /dev/input 下的节点来访问当前的发生的事件并对 原始数据 进行提炼,然后交由 WMS 去分配,WMS 作为 InputEvent 的派发者,将事件派发到指定的 window 去处理,当事件到达应用程序端的时候,就已经变成相对
可理解


好处理

的了。

接下来我们从 InputManagerService 入手,看看 InputEvent 事件是如何一步步变得好处理的。

InputManagerService 的创建

InputManagerService 是由 SystemServer 统一启动

/*framework/base/services/java/com/android/server/SystemServer.java */
private void startOtherServices() {
...
inputManager = new InputManagerService(context);
wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
inputManager.start();
复制代码

从上面可以看出,InputManagerService 的实例直接作为 WindowManagerService 的参数传入,另外, IMS 也将自己注入到了 ServiceManager 中。最后调用 inputManager.start()
启动 IMS。

/*framework/base/services/java/com/android/server/input/InputManagerService.java */
public void start() {
nativeStart(mPtr); //  mPtr 是 IMS 在构造过程中调用 nativeInit 所创建的 NativeInputManagerService 对象
Watchdog.getInstance().addMonitor(this); // 将 IMS 加入监控体系
/* 接下来是一些监听事件处理,这里的实现包含大量的 native 调用 */
}
复制代码

看来主要的事情都是 NativeInputManagerService 来处理的,接下来看看 NativeInputManagerService 的实现

/*framework/base/services/core/jni/com_android_server_input_InputManagerService.cpp*/
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == nullptr) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
}
//  nativeInit 初始化了 NativeInputManager 对象
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_t result = im->getInputManager()->start(); //  getInputManager 返回的是 InputManager 对象
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
复制代码

nativeStart 最终实现的是 InputManager.start()
方法, InputManager 为 IMS 创建了2 个线程,分别是 InputReaderThread
InputDispatcherThread
,其中 InputReaderThread
负责从从驱动节点读取 Event, InputDispatcherThread
专职分发。我们重点关注 Dispatcher 的部分,对于 Reader 部分这里不做细说。

/*framework/native/services/inputflinger/InputManager.cpp*/
InputManager::InputManager(
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mClassifier = new InputClassifier(mDispatcher);
mReader = createInputReader(readerPolicy, mClassifier); // mReader 与 mDispatcher 在这里建立了关系,如此 Reader线程 就有了通道将事件告知 Dispatcher 线程去执行分发流程
initialize();
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher); //mDispatcherThread 的核心实现主要由 InputDispatcher 来实现
}
status_t InputManager::start() {
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputReader thread due to error %d.", result);
mDispatcherThread->requestExit();
return result;
}
return OK;
}
复制代码

接下来主要关注 InputDispatcher 的分发部分,InputDispatcher 将它获知到的系统事件使用 EventEntry 结构表示,可以分为以下几类

enum {
TYPE_CONFIGURATION_CHANGED,
TYPE_DEVICE_RESET,
TYPE_KEY,
TYPE_MOTION
};
复制代码

本文讨论的事件主要是这里的 TYPE_KEY 类型;InputDispatcher 分发事件的核心函数为 InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime)
,其中针对 TYPE_KEY
类型执行的函数为 done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime)

/*framework/native/services/inputflinger/InputDispatcher.cpp*/
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
....
省略了前面一些前期工作与拦截处理
std::vector<InputTarget> inputTargets;
int32_t injectionResult = ***findFocusedWindowTargetsLocked***(currentTime,
entry, inputTargets, nextWakeupTime);
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false;
}
setInjectionResult(entry, injectionResult);
if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
return true;
}
// Add monitor channels from event's or focused display.
addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(entry));
// 分发事件到目标中去
**dispatchEventLocked**(currentTime, entry, inputTargets);
return true;
}
复制代码

dispatchKeyLocked 先通过
findFocusedWindowTargetsLocked()

来寻找目标窗口,成功找到后会将其加入 inputTargets 中,并且将 injectionResult 赋值为 INPUT_EVENT_INJECTION_SUCCEEDED ;最终通过 dispatchEventLocked
将事件分发给目标窗口。


findFocusedWindowTargetsLocked()

能拿到目标窗口,那这里面肯定少不了 WMS 的参与,因为 WMS 才是真正 window 的管家。还记得最开始,IMS 与 WMS 绑定的那些代码吗?具体的讲,WMS 内部 持有一个 InputManagerCallback 的回调 mInputManagerCallback,IMS 又通过 setWindowManagerCallbacks()
函数将 mInputManagerCallback 作为入参传入,这InputManagerCallback 定义了一系列与事件相关的接口,当 Window 变动的时候,WMS 都会通过 InputManagerCallback 来与 IMS 交流。更细点来说,WMS 到 InputDispatcher 的过程中使用到了 InputMonitor 这个”中介”,感兴趣的可以自行去研究。

**dispatchEventLocked()*
这个函数负责将事件通知给应用程序去处理的, InputDispatcher与 InputTarget 通信是通过 InputChannel 来进行的, InputChannel 是通过 socket 来进行通信的。具体流程是 WMS 在调用 addWindow()
方法时候通会通过 InputChannel.openInputChannelPair(name)
方法打开一个双向通道,而这个openInputChannelPair (源码路径/

framework/native/libs/input/InputTransport.cpp */) 在 native 层的实现就是通过 socket 实现的()。

通过上面的分析,我们对 native 层的事件与 WMS 的传递大致有了了解,而我们更为关心的则是应用层的事件传递。我们知道,Activity 调用 addView()
将 Window 添加到 WMS 的过程中会使用到 ViewRootImpl 这个中介,我们就从 ViewRootImpl 开始分析这一流程。

ViewRootImpl 中的事件传递

WMS 与 View 沟通的桥梁是通过 ViewRootImpl 来实现的。事件能回传给 View 的前提是 View 所在的 Window 添加到了 WMS 中,而 View 添加到 WMS 的过程必然会走到 ViewRootImpl.setView()
方法,那么就从这个方法开始跟踪。

/*framework/base/core/java/android/view/ViewRootImpl.java*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
try {
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
catch (RemoteException e) {
mInputChannel = null;
}finally {
...
}
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
// WindowInputEventReceiver 继承了 InputEventReceiver
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
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;
复制代码

如果 Window 支持事件响应,会 new 一个 InputChannel 对象,然后通过 WindowSession.addToDisplay()
将其作为参数传入 WMS ,之后通过 InputEventReceiver 来监听 InputChannel 的回传数据。结合前面**dispatchEventLocked()**中提到的 InputChannel 充当的角色,不难理解,InputEventReceiver 最终能收到 native 层的回传消息。

之后以责任链的形式封装了 InputStage,默认情况下责任链的起点是 NativePreImeInputStage,终点为 SyntheticInputStage,(当然这不是绝对的,mSyntheticInputStage、mFirstInputStage、mFirstPostImeInputStage 这三个参数就是来确定责任链真正的起点的)针对上层 View 的分发逻辑在 ViewPostImeInputStage 中。根据这点,不难猜测到 WindowInputEventReceiver 在处理分发的过程中会将事件分发给这个 InputStage 的责任链去处理。实际流程也是如此。概括来说就是,WindowInputEventReceiver 收到事件后,WindowInputEventReceiver.onInputEvent() 开始执行,它的职责是将已经消费的事件通知底层,将未消费的事件通过 enqueueInputEvent()
加入队, enqueueInputEvent()
中的 doProcessInputEvents()
会不断获取当前需要处理的 InputEvent 并且将事件分发给 InputStage 去处理。当 Event 被 InputStage 处理完毕后,最终将处理结果通过 InputEventReceiver.finishInputEvent()
通知 native 层。

/*framework/base/core/java/android/view/ViewRootImpl.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;
}
// 对 KeyEvent 的额外处理
if (q.mEvent instanceof KeyEvent) {
mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
}
if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q); // 将事件分发给指定 InputStage 去处理
} else {
finishInputEvent(q);
}
}
/**
* Delivers an event to be processed.
*/
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
apply(q, onProcess(q)); // 这里执行 InputStage 的 onProcess 方法
}
}
复制代码

接下来重点关注 ViewPostImeInputStage.onProcess()
方法,对其他 InputStage 感兴趣的同学可以通过翻阅源码自行阅读,流程都差不多的。从 processGenericMotionEvent 中可以看出,事件又被分发给了 View Tree 的根元素 mView ( 大部分情况下是Activity )去处理,如此一来 变开启了 View 的事件分发流程。

/*framework/base/core/java/android/view/ViewRootImpl$ViewPostImeInputStage.java*/
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q); //如果是 KeyEvent 走 KeyEvent 的处理流程
} else {
final int source = q.mEvent.getSource(); // 获取事件源类型
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q); // MotionEvent 类型的事件会执行到这来
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q); // 轨迹球事件类型
} else {
return processGenericMotionEvent(q); // Motion 事件
}
}
}
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
// 将事件传递给 view 的 dispatchPointerEvent 处理
boolean handled = mView.dispatchPointerEvent(event);
return handled ? FINISH_HANDLED : FORWARD;
}
复制代码

View 中的事件分发流程

View 中处理的事件为 TouchEvent ,它可以细分为以下几个重要类型:

  • ACTION_DOWN

    当手势按下就会触发,是后续一切事件的起点,一般会在这里做一些状态初始化工作。

  • ACTION_MOVE

    当手势按下并拖动,就会产生 ACTION_MOVE 事件。

  • ACTION_UP

    ACTION_UP 是手势操作的结束点。这里通过与 ACTION_DOWN 时候设置的参数进行比较就可以判断是否为长按事件等。

  • ACTION_CANCEL

    ACTION_CANCEL 事件不由用户主动产生,而是系统通过判断后得出的结果。可以简单看做是手势结束的标识,处理类似于 ACTION_UP,并做好清理工作。

View 中的 TouchEvent 投递流程

/*framework/base/core/java/android/view/View.java */
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
复制代码

View 中分发 TouchEvent 还是比较简单的,主要考虑到了两个因素:

  • onTouch

    View 对象可以通过 setOnTouchListener 来设置一个 Event 的监听者,对应上面的 li.mOnTouchListener ,这样当事件来临时,View 会主动调用这个监听者的 onTouch 方法去消费事件。

  • onTouchEvent

    用户没有指定 TouchListener ,或者 flag 指定为 disable, 亦或是用户指定了 TouchListener 但是 TouchListener.onTouch() 返回值为 false 的情况下,系统才会将 event 传递给 onTouchEvent 。

ViewGroup 中的 TouchEvent 投递流程

ViewGroup 的 TouchEvent 投递流程相对复杂一些,因为涉及到对子对象的处理,多了一个拦截的概念。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// ACTION_DOWN 事件来临,先清除掉之前的所有状态
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检查拦截情况
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 如果disallowIntercept 为 false,调用 onInterceptTouchEvent 方法
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false; //不拦截
}
} else {
intercepted = true; //拦截
}
// 检查 CANCEL 的情况
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
//不拦截的情况
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
for (int i = childrenCount - 1; i >= 0; i--) {
// 省略了循环查找一个能处理此事件的 child 的过程
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
...
break;
}
....
return handled
}
复制代码

ViewGroup 的 dispatchTouchEvent 首先会检查是否被拦截,用intercepte来表示,如果 intercepted 为 false,表明 ViewGroup 不希望拦截这一消息,因而它的子对象才有机会来处理它。如果 intercepted 为 true,说明 ViewGroup 需要拦截这个事件自己来处理,之后通过 dispatchTransformedTouchEvent()
super.dispatchTouchEvent()
触发 onTouchEvent()
回调。

intercepted 要为 true 需要满足两个条件:

  • disallowIntercept 为 false

    可以通过调用 ViewParent 接口的 requestDisallowInterceptTouchEvent(true) 使 disallowIntercept 为false,Android 中仅 ViewGroup 和ViewRootImpl 继承了 ViewParent 接口,而 ViewRootImpl 中的 requestDisallowInterceptTouchEvent 是一个空实现。

    也就是说,只有 ViewGroup.requestDisallowInterceptTouchEvent(true)
    有效。

  • onInterceptTouchEvent() 返回值为 true

    onInterceptTouchEvent() 可以体现出每个 ViewGroup “差异化”的地方,这个方法是 Android 鼓励大家在继承 ViewGroup 时候重载的方法。

canReceivePointerEvents()
用于判断当前 child 是否能接收 PointerEvents, dispatchTransformedTouchEvent()
则计算(x,y)这个点有没有落在此 child 区域; 如果 child 不符合要求,直接跳过,可以节省时间。如果找到了符合要求的 child,就通过 dispatchTransformedTouchEvent()
来调用 child.dispatchTouhEvent()
,这个 child 有可能是 View,也有可能是 ViewGroup。

Activity 的事件分发

Activity 的 dispatchTouchEvent 首先会将事件交给它的 window 对象去处理,Activity 的 getWindow() 得到的 Window 对象是 PhoneWindow,PhoneWindow 的 superDispatchTouchEvent() 方法又将事件分发给 mDecor 去处理,这个 mDecor 是一个 FrameLayout,是在 Activity 调用 setContentView() 时生成的。具体的流程这里不再展开,对这块有疑问的同学可以自行跟踪 setContentView() 的调用流程。FrameLayout 又是继承 ViewGroup 的,如此便和上面 View 的 dispatchTouchEvent 建立了关联。

/*framework/base/core/java/android/app/Activity.java*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
复制代码
/*framework/base/core/java/com/android/internal/policy/PhoneWindow.java*/
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
复制代码
  • 每当 MotionEvent 事件分发到 Activity 都会调用到 onUserInteraction(),这是一个空实现,可以利用这个方法做一些全局性的事件监听。
  • 如果 getWindow().superDispatchTouchEvent(ev) 返回值为 true,表示事件已经被分发到指定对象,在此过程中事件将会传递到这个对象的 onTouchEvent 中去
  • 如果 getWindow().superDispatchTouchEvent(ev) 返回值为 false,Activity 的 onTouchEvent 会被执行。
  • 通过重载 Activity 的 dispatchTouchEvent,并使其返回 false,根据 ViewRootImpl 流程分析中提到的内容,processPointerEvent 则会结束 ViewPostImeInputStage 的分发流程,转到 SyntheticInputStage 去处理。也就是说 Activity 将不再响应任何 MotionEvent 事件。

微信扫一扫,分享到朋友圈

Android中的事件分发机制

CI/CD with Cloud-Native Applications

上一篇

The complete guide to Jupyter Notebooks for Data Science

下一篇

你也可能喜欢

Android中的事件分发机制

长按储存图像,分享给朋友