本文是综合
《深入理解 Android 卷 III(第五章 深入理解 Android 输入系统)》
《Android 系统源代码情景分析(第 14章 Android 应用程序的键盘消息处理机制)》
《深入解析 Android 5.0 系统(第 16 章 Android 的输入管理)》
三章的总结。
. Linux 内核
接受输入设备的中断,并将原始事件的数据写入设备节点中;
. 设备节点
作为内核月 IMS 的桥梁,将原始事件的数据暴露给用户空间, 以便 IMS 可以从中读取事件;
. EventHub
直接访问所有的设备节点,将所有的输入事件通过 getEvents() 接口把多个输入设备节点中读取的数据交给 InputReader, 它是输入系统最底层的一个组件;
. InputReader
运行独立线程中,负责管理输入设备的列表与配置, 对输入事件进行加工处理,然后交给 InputDispatcher 处理;
. InputDispatcher
运行独立线程中,保存来至 WMS 的窗口信息,将从 InputReader 传过来的输入事件派发到合适的窗口;
. InputReaderPolicy
为 InputReader 的事件加工处理提供一些策略配置;
. InputDispatcherPolicy
为 InputDispatcher 的派发过程提供策略控制,例如截取某些特定的输入事件作为特殊用途,或者阻止将某些事件派发给目标窗口。例如 Home 键事件被截取到 PhoneWindowManager 中处理,并阻止窗口收到 Home 键按下的事件;
. WindowManagerService
当创建窗口时 WMS 为新窗口和 IMS 创建事件传递的通道 InputChannel;
. ViewRootImpl
对某些窗口,例如壁纸, SurfaceView 的窗口,窗口就是事件派发的终点;
对于其他例如 Activity, 对话框等使用了 Android 控件系统的窗口来说,输入终点是 View。 ViewRootImpl 将窗口所接收的输入事件沿着控件树将事件派发给感兴趣的控件。
整体流程:
内核将原始事件写入设备节点中, InputReader 不断地通过 EventHub 将原始事件取出来并翻译加工成 Android 输入事件,然后交给 InputDispatcher 。InputDispatcher 根据 WMS 提供的窗口信息将事件交给合适的窗口。窗口的 ViewRootImpl 对象再沿着控件树将事件派发给刚兴趣的控件。
inputManager = new InputManagerService(context, wmHandler);
wm = WindowManagerService.main(context, power, display, inputManager,
uiHandler, wmHandler,
factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
!firstBoot, onlyCore);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp& looper) :
mLooper(looper) {
JNIEnv* env = jniEnv();
...
// 创建 EventHub
sp eventHub = new EventHub();
// 创建 Native 层的 InputManager
mInputManager = new InputManager(eventHub, this, this);
}
InputManager::InputManager(
const sp& eventHub,
const sp& readerPolicy,
const sp& dispatcherPolicy) {
// 创建 InputDispatcher
mDispatcher = new InputDispatcher(dispatcherPolicy);
// 创建 InputReader
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
void InputManager::initialize() {
// 为 InputReader 创建运行线程 InputeReaderThread
mReaderThread = new InputReaderThread(mReader);
// 为 InputDispatcher 创建运行线程 InputDispatcherThread
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
IMS 创建完成后, 在 ServerThread 中调用 InputManagerService.start() 启动。
启动后,
InputReader 在其线程循环中不断从 EventHub 中抽取原始输入事件,进行处理加工,然后将事件方法 InputDispatcher 的派发队列中;
InputDispatcher 则在其线程循环中将派发队列中的事件取出,查找合适窗口,并将事件写入窗口的事件接收管道中 InputChannel;
窗口事件接收线程的 Looper 从管道中将事件取出,交给事件处理函数进行事件响应。
EventHub
EventHub 的 getEvnts() 方法利用 Linux 提供的两套机制 INotify 与 Epoll 解决事件的读取问题。
InputReader 的总体流程
InputReader 运行在 InputReaderThread 线程中,当线程运行后,会不断的调用 threadLoop() ,直到此函数返回 false, 则退出线程循环,从而结束线程。
framework/services/input/InputManager.cpp
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
void InputReader::loopOnce() {
...
// 1. 从 EventHub 中抽取未处理的事件列表;
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
AutoMutex _l(mLock);
...
if (count) {
// 2. 通过 processEventsLocked() 对事件进行处理, 并将处理后的结果
// 暂存到 mQueueListener 中
processEventsLocked(mEventBuffer, count);
}
...
} // release lock
...
// 3. 调用 mQueueListener.flush() 方法将所有暂存的输入事件一次性地交付给
// InputDispatcher
mQueuedListener->flush();
}
过程:
先将输入事件放入 Connection 的 outboundQueue 队列中,然后在由 mInputPublisher 依次将队列中的事件封装为 InputMessage 并写入 Service InputChannel, 直到队列为空或 Service InputChannel 的写入缓冲区满。
写入的事件将被移到 waitQueue 队列里。随后派发线程进入休眠状态。
当窗口在另一端读取事件并发来反馈后,派发线程因 Service InputChannel 可读而被唤醒,并在 handleReceiveCallback() 中通过 Connection 的 mInputPublisher 读取反馈信息,将其与 waitQueue 中等待反馈的事件进行配对成功后,将事件从 waitQueue 中移除,完成事件的派发过程。
InputDispatcher 与 窗口通过 InputChannel 通信;
InputChannel 本质是一对 SocketPair ,而 SocketPair 用来实现在本机内进行进程间的通信, InputChannel 是 SocketPair 描叙符及其操作的封装,而且是成对使用的
framework/libs/androidfw/InputTransport.cpp
status_t InputChannel::openInputChannelPair(const String8& name,
sp& outServerChannel, sp& outClientChannel) {
int sockets[2];
// 通过 socketpair(...) 创建一对 SocketPair, 并保存在 sockets 数组中
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
...
return result;
}
...
// 创建 Server 端的 InputChannel 对象
outServerChannel = new InputChannel(serverChannelName, sockets[0]);
...
// 创建 Client 端的 InputChannerl 对象
outClientChannel = new InputChannel(clientChannelName, sockets[1]);
return OK;
}
3.将信息封装成 InputMessage 在 两个 InputChannel 之间通信
4.在 WMS添加窗口是,会创建一对 InputChannel, 其中一个保存在 WindowState 中,并注册给 IMS, 这是 server InputChannel.
另外一个则通过传出参数 outChannel 交给调用者,这是 client InputChannel.
framework/services/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
// 创建 InputChannels
if (outInputChannel != null && (attrs.inputFeatures &WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
String name = win.makeInputChannelName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
// Service 端的 InputChannel
win.setInputChannel(inputChannels[0]);
// Client 端的 InputChannel
inputChannels[1].transferTo(outInputChannel);
// InputManagerService , 将 WindowState 所保存的
// InputChannel 向 InputManagerService 注册
mInputManager.registerInputChannel(win.mInputChannel,win.mInputWindowHandle);
}
// 将所有窗口信息更新到 InputManagerService 中
mInputMonitor.updateInputWindowsLw(false /*force*/);
}
5.Client 端对事件的接收
当 InputPublisher 将事件以 InputMessage 的形式写入 inputChannel 中之后, 窗口端的 Looper 会因此而被唤醒,并执行 NativeInputEventReceiver 的 handleEvent().
framework/core/jni/android_view_InputEventReceiver.cpp
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
bool consumeBatches, nsecs_t frameTime) {
...
ScopedLocalRef receiverObj(env, NULL);
bool skipCallbacks = false;
for (;;) {
uint32_t seq;
InputEvent* inputEvent;
// 1. consume() 从 InputChannel 中读取一条 InputMessage,解析为 InputEvent
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
...
if (!skipCallbacks) {
...
// 2. 根据事件类型创建 Java 层的 KeyEvent 和 MotionEvent
jobject inputEventObj;
switch (inputEvent->getType()) {
case AINPUT_EVENT_TYPE_KEY:
...
// 创建 KeyEvent 事件
inputEventObj = android_view_KeyEvent_fromNative(env,
static_cast(inputEvent));
break;
case AINPUT_EVENT_TYPE_MOTION:
...
// 创建 MotionEvent 事件
inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
static_cast(inputEvent));
break;
default:
...
}
if (inputEventObj) {
...
// 3. 通过 JNI 回调 Java 层的 InputEventReceiver 的
// dispatcheInputEvent() 方法
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
...
} else {
...
}
}
...
}
}
framework/core/java/android/view/InputEventReceiver.java
InputEventReceiver.dispatchInputEvent(…)
private void dispatchInputEvent(int seq, InputEvent event) {
// 以 event 的序列号为键,将来自 InputDispacher 的序列号保存到 Map中。java 层
// 在创建时也会分配一个唯一的序号,用来进行唯一标识。
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
至此,我们的事件传递过程终于回到 Java 层了,整个事件的接收部分工作完成了。 onInputEvent() 由使用者重写,从而实现各种各样的工作。
注: 按键事件与通用事件的派发流程相同基本相同
不同点:
1. 按键事件通过 notifykey() 方法进入 InputDispatcher;
2. 按键事件的派发目标通过焦点方式进行查找目标窗口;
前面我们的 InputEvent 已经到 InputEventReceiver.onInputEvent() 方法,而在 ViewRootImpl.java 中 WindowInputEventReceiver extends InputEventReceiver
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
// 调用了 ViewRootImpl.enqueueInputEvent(...) 方法
enqueueInputEvent(event, this, 0, true);
}
@Override
public void onBatchedInputEventPending() {
scheduleConsumeBatchedInput();
}
@Override
public void dispose() {
unscheduleConsumeBatchedInput();
super.dispose();
}
}
ViewRootImpl.enqueueInputEvent(…) 方法最终会调用 InputStage.deliver() 方法
InputStage 在创建的时候,利用前一个 InputStage 传进构造方法里,形成流水线对消息的处理。
流水线的创建
framewoke/core/java/android/view/ViewRootImpl.java
InputStage syntheticStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticStage);
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;
ViewPreImeInputStage
final class ViewPreImeInputStage extends InputStage {
public ViewPreImeInputStage(InputStage next) {
super(next);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
}
return FORWARD;
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// mView 是 DecorView
if (mView.dispatchKeyEventPreIme(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
}
processKeyEvent() 方法调用了 mView.dispatcheKeyEventPreIme(…) 方法。这里的 mView 是 DecorView.这将导致 Activity 的控件树中所有的 View 的 onKeyPreIme() 方法被调用。这样就给了 * View 在输入法处理 Key 事件前先得到消息并处理的机会。
ViewPostImeStage
*/
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 都是 DecorView
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
}
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
if (mView.dispatchPointerEvent(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
private int processTrackballEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
if (mView.dispatchTrackballEvent(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
private int processGenericMotionEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
// Deliver the event to the view.
if (mView.dispatchGenericMotionEvent(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
最后事件都是调用 DecorView 中的方法出来各类的输入事件,回调到 Activity 中的回调方法。