【读书笔记】Android(安卓)输入系统
本文是综合
《深入理解 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 对象再沿着控件树将事件派发给刚兴趣的控件。
二、IMS 的结构体系
- InputManagerService 在 SystemService 中的 ServerThread 线程中启动;
SystemServer.java 的 ServerThread.run() 方法中
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();
- 在 InputManagerService 的构造函数中,调用 nativeInite(…) 方法,在该方法内创建 NativeInputManager
framework/services/jni/com_android_input_InputManagerService.cpp
NativeInputManager::NativeInputManager(…)
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 的构造函数中,创建 InputReader 和 InputDispatcher
framework/services/input/InputManager.cpp
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();}
三、 InputDispatcher 与窗口的通信
过程:
先将输入事件放入 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() 方法
- 理解 InputStatge
对
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 和 ViewPostImeStage
framewoke/core/java/android/view/ViewRootImpl.java
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 中的回调方法。
更多相关文章
- Android之菜单总结
- Android使用Retrofit进行网络请求
- Android官方入门文档[1]创建一个Android项目
- android 创建桌面快捷方式 、插件
- AIR Native Extension的使用(Android)一 : 打包ane
- 创建android逐帧动画的两种方式
- [android]在上下文菜单的选中事件中获取列表选中的元素
- 使用sencha cmd创建android应用
- android 多点触控