【读书笔记】Android 输入系统

本文是综合
《深入理解 Android 卷 III(第五章 深入理解 Android 输入系统)》
《Android 系统源代码情景分析(第 14章 Android 应用程序的键盘消息处理机制)》
《深入解析 Android 5.0 系统(第 16 章 Android 的输入管理)》
三章的总结。

一、输入系统的总体流程

【读书笔记】Android 输入系统_第1张图片

. 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 的结构体系

【读书笔记】Android 输入系统_第2张图片

  1. 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();
  1. 在 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);
}
  1. 在 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() 启动。

  1. 启动后,
    InputReader 在其线程循环中不断从 EventHub 中抽取原始输入事件,进行处理加工,然后将事件方法 InputDispatcher 的派发队列中;

    InputDispatcher 则在其线程循环中将派发队列中的事件取出,查找合适窗口,并将事件写入窗口的事件接收管道中 InputChannel;

    窗口事件接收线程的 Looper 从管道中将事件取出,交给事件处理函数进行事件响应。

  2. EventHub
    EventHub 的 getEvnts() 方法利用 Linux 提供的两套机制 INotify 与 Epoll 解决事件的读取问题。

  3. 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 与窗口的通信

【读书笔记】Android 输入系统_第3张图片

过程:
先将输入事件放入 Connection 的 outboundQueue 队列中,然后在由 mInputPublisher 依次将队列中的事件封装为 InputMessage 并写入 Service InputChannel, 直到队列为空或 Service InputChannel 的写入缓冲区满。

写入的事件将被移到 waitQueue 队列里。随后派发线程进入休眠状态。

当窗口在另一端读取事件并发来反馈后,派发线程因 Service InputChannel 可读而被唤醒,并在 handleReceiveCallback() 中通过 Connection 的 mInputPublisher 读取反馈信息,将其与 waitQueue 中等待反馈的事件进行配对成功后,将事件从 waitQueue 中移除,完成事件的派发过程。

  1. InputDispatcher 与 窗口通过 InputChannel 通信;

  2. 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 之间通信
【读书笔记】Android 输入系统_第4张图片

【读书笔记】Android 输入系统_第5张图片

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() 方法

  1. 理解 InputStatge
    【读书笔记】Android 输入系统_第6张图片

InputStage 在创建的时候,利用前一个 InputStage 传进构造方法里,形成流水线对消息的处理。
【读书笔记】Android 输入系统_第7张图片

流水线的创建
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;
  1. 这里注意看看 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)