[Framework] 理解 Android 中的 Input 事件
在 Andorid 中的 Input
事件包括屏幕点击、按键点击(包括软硬键盘、三大金刚按键等等)和鼠标(没错 Android 是支持外置鼠标的,只是一般应用开发用不上),通常用户做了 Input
的操作后我们的应用作为反应会触发 UI
的重新绘制和一些其他的业务逻辑,在这个过程中对用户的体验非常重要,经常在这个过程也会出现卡顿和 ANR
,通过了解 Input
事件可以更加深入的理解卡顿和 ANR
。
InputManagerService
我们的输入事件都是由 IMS
管理的,它和 AMS
一样工作在 system_server
进程。 IMS
在初始化的过程中会初始化以下几个对象 EventHub
,InputReader
和 InputDispatcher
。 他们的工作方式在一定的程度上非常像 Handler
的工作方式,EventHub
对应 MessageQueue
,InputReader
对应 Looper
,InputDispatcher
对应 Handler
,如果不熟悉 Handler
可以参考我之前写的文章:Android Handler 工作原理。
EventHub
InputReader
会通过 EventHub#getEvents()
方法去获取事件,如果没有事件就会阻塞(这里和 Handler
一样,InputReader
也是一个死循环无限获取,没有就阻塞,也是通过 epoll
机制唤醒)。 EventHub
会监听 /dev/input
的文件来获取事件,获取到的事件是input_event
对象,它会把它转换成 RawEvent
(其中包含 deviceId
,用来标识是哪个 input
文件的事件)对象,最后把这个事件返回给 InputReader
。
InputReader
在前面说到 InputReader
会循环调用 Event#getEvents()
方法去获取 Input
事件,返回的对象是 RawEvent
,InputReader
会根据 RawEvent
中的 deviceId
去检查本地是否已经有创建 InputDevice
对象,如果没有就会根据 deviceId
去创建对应的 InputDevice
对象,在 InputDevice
中会创建一个非常重要的 Mapper
对象,他们是 RawEvent
事件的翻译官,需要用他们来解释 RawEvent
中的数据,其中有 KeyboardInputMapper
(键盘)、CursorInputMapper
(鼠标)、MultiTouchInputMapper
(屏幕多点触控) 和 SingleTouchInputMapper
(屏幕单点触控)。
InputDevice
中的 Mapper
会把 RawEvent
对象转换成 NotifyArgs
对象 (其中的数据已经是被翻译过的了,例如键盘输入事件就有 keyCode
了,触摸事件就有 x
和 y
了,主要有以下几类 NotifyKeyArgs
, NotifyMotionArgs
, NotifyDeviceResetArgs
, NotifySwitchArgs
, - NotifyConfigurationChangedArgs
)。翻译过的 NotifyArgs
对象,会被放在 mArgsQueue
队列里面等待处理。
处理 NotifyArgs
事件对象时,会通过 mPolicy
对象的 interceptKeyBeforeQueueing()
方法和 filterInputEvent()
方法去修改这个事件对象和拦截这个事件对象,默认他们的实现都是空,最终这个方法会调用到 IMS
对应的方法中去。NotifyArgs
对象会被转换成 Event
(KeyEvent
, MotionEvent
) 对象,Event
又会被转成 EventEntry
(KeyEventEntry
,MotionEventEntry
) 存放在 mInboundQueue
队列中,然后唤醒(也是通过 epoll
唤醒) InputDispatcher
去处理 mInboundQueue
中的数据。
InputDispatcher
InputDispatcher
和 InputReader
一样他们工作都是一个死循环,InputDispatcher
一直在等待 InputReader
读到 EventEntry
后去通知它,收到通知后就会去读 InputReader#mInboundQueue
中的事件,在这里还会重制 ANR
信息 (后面写文章单独讲 ANR
),然后判断该事件是否应该丢弃,如果丢弃流程就结束了,如果不丢弃就检查是否有一个有焦点的 Window
来处理它,如果有就会把这个 Window
添加到 inputTargets
中,这个 Window
中有一个非常重要的对象 inputChannel
,它就是用来和应用层通信的对象。然后在 inputChannel
中找到一个可用的 connection
,然后把 InputReader#mInboundQueue
中的事件读取到 outBoundQueue
(会把 EventEntry
转换成 DispatchEntry
) 中,然后通过 connection
发送至应用进程,把这个已经发送的事件还会添加到 waitQueue
中,表示还在等待应用进程回复的事件。这个 connection
的通信方式是 Socket
,那么它是什么时候创建的呢?这就要看 Window
的添加流程了。
事件通信对象 InputChannel 的创建
我在 聊聊 Android Activity 中的 Token 文章中有聊到这个过程,在应用层所有的 UI 显示最终都会调用到 ViewRootImpl#setView()
方法,在这个方法中会调用由 WMS
生成的 Session
的 addToDisplay()
方法去请求添加 Window
:
Java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
// ...
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
// ...
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
}
}
其中 mInputChannel
就是在 InputDispatcher
中提到的 inputChannel
,addToDisplay()
方法调用完成后会通过 mInputChannel
构建一个 WindowInputEventReceiver
对象用来处理 IMS
传递过来的事件。
我们在来看看 WMS
的处理:
Java
final class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
}
Java
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) {
// ...
WindowState win = new WindowState(this, session, client, token,
attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
if (outInputChannel != null && (attrs.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
//根据WindowState的HashCode以及title来生成InputChannel名称
String name = win.makeInputChannelName();
//创建一对InputChannel
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
//将socket服务端保存到WindowState的mInputChannel
win.setInputChannel(inputChannels[0]);
//socket客户端传递给outInputChannel
inputChannels[1].transferTo(outInputChannel);
//利用socket服务端作为参数
mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
}
// ...
boolean focusChanged = false;
if (win.canReceiveKeys()) {
//新添加window能接收按下操作,则更新聚焦窗口。
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
false /*updateInputWindows*/);
}
...
if (focusChanged) {
mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
}
//设置当前聚焦窗口
mInputMonitor.updateInputWindowsLw(false /*force*/);
}
这里会创建一个 InputChannel
对,服务端通过 IMS#registerInputChannel
传递给 IMS
,客户端通过 binder
传递给应用进程。
应用进程处理 Input 事件
在讲 ViewRootImpl#setView()
的时候说到 Session#addToDisplay()
方法调用完成后会用创建好的 inputChannel
构建一个 WindowInputReceiver
对象来接收 IMS
传递来的事件,其中回调的方法是 onInputEvent()
:
Java
@Override
public void onInputEvent(InputEvent event, int displayId) {
enqueueInputEvent(event, this, 0, true);
}
Java
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
// ...
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
Java
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) {
// ...
deliverInputEvent(q);
}
// We are done processing all input events that we can process right now
// so we can clear the pending flag immediately.
if (mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = false;
mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
}
}
Java
private void deliverInputEvent(QueuedInputEvent q) {
// ...
if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
最终事件下发会调到 deliverInputEvent()
方法,这个 state
就是处理任务的地方,如果 state
为空就表示,已经处理完毕,调用 finishInputEvent()
方法,最终会通过 inputChannel
通知 IMS
,如果超过一定的时间没有处理完成也就会出现 ANR
。(后续文章再讨论这个问题)
其中上面描述的 state
有以下几种:
Java
// ...
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);
// ...
其中 ViewPostImeInputState
就是下发我们 view
的触摸事件,对应的方法如下:
Java
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
boolean handled = mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
这个 mView
也就是 DecorView
,然后通过 dispatchPointerEvent()
方法把,触摸事件传递到整个 ViewTree
,直到有 View
处理。
参考文章
Input系统---启动篇
Input系统---InputReader线程
Input系统---InputDispatcher线程
Input系统---UI线程