从源码角度看 InputManagerService

简介

上篇文章《从源码角度看Activity显示视图流程》中,我从Activity被成功创建开始分析,到 ViewRootImpl 进行控件树的绘制,最后再到 ViewRootImpl 与 WMS 的通信。这期间其实涉及到了 InputManagerService 的一些初始化工作,里面 InputChannel 初始化的代码我都没有继续深入分析

这篇文章里,我将系统的分析 IMS 的源码,照惯例先放出一张整体的类图与流程图,方便把握整体。最后分了三个小节分别去介绍其中的细节

文章将会分析到 InputEventReceive.onInputEvent 方法,这个方法是客户端事件分发机制的入口,下片文章里我将从 onInputEvent 为入口开始分析

查看大图

整体流程图

查看大图

  1. 用户轻点屏幕,linux 内核产生中断,向 /dev/input/ 目录下的设备文件 eventxx 下入数据
  2. native 层,EventHub 通过 epoll 监测到文件被写入,使用 inotify 读取文件中的数据
  3. InputReader 将事件数据解析成装满 RawEvent 的缓冲区中,随后批量使用 InputMapper 进行处理
  4. 用户的触摸事件最终被加工为 NotifyMotionArgs,随后被批量插入到 InputDispatcher 的队列中
  5. InputDispacher 从队列中取出 EventEntry 数据进行派发
  6. 获取触摸事件目标窗口列表,使用 socketpair 向客户端发送输入事件
  7. 客户端在将事件分发到各个窗口,处理完毕后会调用 finish 告诉服务端事件已经处理完成
  8. InputDispatcher 收到事件处理完成通知,重新初始化 ANR 相关变量

IMS 初始化与启动

InputManagerService 读取事件数据、解析并派发事件到客户端的逻辑都是在 native 实现的,java 层所做的工作不过是接收并反馈事件处理的结果

这一节对应着整体流程的初始化步骤,如epoll 与 inotify 对于输入事件设备文件的初始化,有了它们 android 才得以以高效的方式获取用户输入事件的数据;如InputReaderThread 和 InputDispatcherThread 两个重要线程,才使得整个 ims 服务的工作流程犹如工厂生产线一般前后贯通

查看大图

初始化 epoll 与 inotify

frameworks/base/services/java/com/android/server/SystemServer.java

private void startOtherServices() {
    ...
    Slog.i(TAG, "Input Manager");
    inputManager = new InputManagerService(context);
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
    inputManager.start();
    ...
}

和WMS, AMS 这些重要系统服务一样,IMS也是在SystemServer.startOtherServices中进行初始化,并调用 start 方法启动

frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

public InputManagerService(Context context) {
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
}

InputManagerService 中,直接调用 nativeInit 在 native 中进行初始化

frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {

    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    ...

    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

初始化 EventHub 与 InputManager,其中 EventHub 的本质要封装了 epoll 与 inotify,监听设备写入的事件并读取到缓冲中进行简单解析

frameworks/native/services/inputflinger/EventHub.cpp

EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
        mOpeningDevices(0), mClosingDevices(0),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);

    // 新建一个 epoll
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);

    // 初始化 inotify
    mINotifyFd = inotify_init();
    // 监听设备的创建与删除
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s.  errno=%d",
            DEVICE_PATH, errno);

    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    // 增加需要监听的设备 fd
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
     ...
}

epoll 初始化完成后,当输入设备被写入数据后,epoll_wait 就会返回数据,并可以对其进行解析

启动 IMS

frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

public void start() {
    Slog.i(TAG, "Starting input manager");
    nativeStart(mPtr);
    ...
}

调用 native 方法,启动 InputDispatcherThread 与 InputReaderThread 两个重要线程

frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    status_t result = im->getInputManager()->start();
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}

通过 JNI 调用到 native 后,调用 InputManager 的 start 方法启动 IMS 服务

frameworks/native/services/inputflinger/InputManager.cpp

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);

    return OK;
}

可以看到,InputManager 只是启动了两个 native 线程,循环来处理输入事件的数据

frameworks/native/services/inputflinger/InputReader.cpp

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}

frameworks/native/services/inputflinger/InputDispatcher.cpp

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}

以上,通过两个线程不断调用 loopOnce 与 dispatchOnce,IMS 的基本服务已经初始化完毕,接下来进入正题,从 InputReader 读取输入设备事件开始分析 IMS 是如何一步一步的将设备中的数据分发到用户能看到的视图中的

一些背景知识

  • 内核中断: 当触屏驱动被挂载到对应的 /dev/input/event0 后,触摸屏幕会导致触摸屏的引脚电平变低,随后CPU引脚监测到电压的变化就会产生中断,中断处理程序就会读取触屏的数据
  • inotify: 实现 /dev/input 目录文件状态的监听
  • epoll: I/O 多路复用机制,使用 epoll_ctl 函数进行注册监听 inotify 的文件句柄,当事件到来时会采用类似回调的方式执行回调方法

输入事件的读取与解析

查看大图

当 epoll, inotify 与两个线程被初始化,当事件到来时,InputReader 便会率先开始工作。这一节中,IuputReader 会从 EventHub 中读取数据,随后会交给对应的 InputMapper 将事件的类型进行加工。加工完毕后会将事件数据批量的插入到 InputDispatcher 的队列中以供派发

InputReader.loopOnce:InputReaderThread 的一次读取

frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::loopOnce() {
...
    // 从 EventHub 中读取数据
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();

        if (count) {
              // 如果有数据则进行处理
            processEventsLocked(mEventBuffer, count);
        }

...

        if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            getInputDevicesLocked(inputDevices);
        }
    } // release lock
...
    // 讲添加进缓存队列中的事件一齐插入到 InputDispatcher 的队列中
    mQueuedListener->flush();
}

这段代码基本可以概括 InputReader 的主要职责,逻辑很清晰。其中 EventHub.getEvents 如果获取到了数据才会进行到下一个状态中,大部分时间里,如果没有输入事件 getEvents 将会产生阻塞,不会消耗 CPU 资源

EventHub.getEvents:IMS 的心跳

frameworks/native/services/inputflinger/EventHub.cpp

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ALOG_ASSERT(bufferSize >= 1);

    AutoMutex _l(mLock);

    struct input_event readBuffer[bufferSize];

    // 事件的获取不是一个一个的获取,而是采用缓冲队列的形式,分批去获取的
    RawEvent* event = buffer;

    for (;;) {
        while (mPendingEventIndex < mPendingEventCount) {
            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];

            ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
            Device* device = mDevices.valueAt(deviceIndex);
            if (eventItem.events & EPOLLIN) {
                // 读取 mPendingEventItems 中的设备数据
                int32_t readSize = read(device->fd, readBuffer,
                        sizeof(struct input_event) * capacity);
              for (size_t i = 0; i < count; i++) {
                      // 封装成 input_event
                    struct input_event& iev = readBuffer[i];
                    ...
                    event->deviceId = deviceId;
                    event->type = iev.type;
                    event->code = iev.code;
                    event->value = iev.value;
                    event += 1;
                    capacity -= 1;    
              }
            }
            ...
            // epoll 监测到设备可读
            int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    }
return event - buffer;

InputReader.processEventsLocked: 预处理输入事件

以上的代码是个大致的逻辑,当通过 epoll 获取到可读设备的 fd 后,下一个循环将会读取设备中的数据

frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            int32_t deviceId = rawEvent->deviceId;
...
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}

void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
...

    device->process(rawEvents, count);
}

可以看到获取到的事件数据是委托给 InputDevice 调用 process 方法来进行处理的

frameworks/native/services/inputflinger/InputReader.cpp#InputDevice

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
...
    for (size_t i = 0; i < numMappers; i++) {
        InputMapper* mapper = mMappers[i];
        mapper->process(rawEvent);
    }
...
}
void TouchInputMapper::process(const RawEvent* rawEvent) {
...

    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
        sync(rawEvent->when);
    }
}

void TouchInputMapper::sync(nsecs_t when) {
    const RawState* last = mRawStatesPending.isEmpty() ?
            &mCurrentRawState : &mRawStatesPending.top();

...

    processRawTouches(false /*timeout*/);
}

void TouchInputMapper::cookAndDispatch(nsecs_t when) {
...
    dispatchPointerUsage(when, policyFlags, pointerUsage);
...
}

void TouchInputMapper::dispatchPointerUsage(nsecs_t when, uint32_t policyFlags,
        PointerUsage pointerUsage) {
...

    switch (mPointerUsage) {
    case POINTER_USAGE_STYLUS:
        dispatchPointerStylus(when, policyFlags);
        break;
    default:
        break;
    }
}

void TouchInputMapper::dispatchPointerStylus(nsecs_t when, uint32_t policyFlags) {
...

    dispatchPointerSimple(when, policyFlags, down, hovering);
}

void TouchInputMapper::dispatchPointerSimple(nsecs_t when, uint32_t policyFlags,
        bool down, bool hovering) {
...

    if (mPointerSimple.down && !down) {
        mPointerSimple.down = false;

        // Send up.
        // 将事件封装成 NotifyMotionArgs,随后调用 QueuedInputListener 接口插入到缓存队列中
        NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
                 AMOTION_EVENT_ACTION_UP, 0, 0, metaState, mLastRawState.buttonState, 0,
                 mViewport.displayId,
                 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
                 mOrientedXPrecision, mOrientedYPrecision,
                 mPointerSimple.downTime);
        getListener()->notifyMotion(&args);
    }
...
}

这一步事件的处理逻辑很复杂,我不准备详细分析了。我这边列出的是精简的关于触摸类型的事件处理,只需记得事件处理完成后,最后的输出结果是将封装好的 NotifyMotionArgs 插入到 QueuedInputListener 队列中即可

frameworks/native/services/inputflinger/InputListener.cpp#QueuedInputListener

void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
    mArgsQueue.push(new NotifyMotionArgs(*args));
}

void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}

可以看到, 调用notifyXXX系列方法最终都是插入到名为 mArgsQueue 的队列中。当缓冲队列中的所有事件都处理完毕之后,InputReader 会调用 flush 批量将队列中的事件进行 notify

QueuedInputListener.notify:插入到 InputDispatcher 队列

frameworks/native/services/inputflinger/InputListener.cpp#QueuedInputListener

void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyMotion(this);
}

以触摸事件为例,notify实际上调用的是 listener 的notifyMotion,而 listener 对象实际是 InputDispatcher 的一个引用,所以最终调用的还是 InputDispatcher 的notifyMotion方法

```cpp
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
...
uint32_t policyFlags = args->policyFlags;
policyFlags |= POLICY_FLAG_TRUSTED;
mPolicy->interceptMotionBeforeQueueing(args->eventTime, /byref/ policyFlags);

bool needWake;
{ // acquire lock
    mLock.lock();

...

    // Just enqueue a new motion event.
    MotionEntry* newEntry = new MotionEntry(args->eventTime,
            args->deviceId, args->source, policyFlags,
            args->action, args->actionButton, args->flags,
            args->metaState, args->buttonState,
            args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
            args->displayId,
            args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);

    needWake = enqueueInboundEventLocked(newEntry);
    mLock.unlock();
} // release lock

if (needWake) {
    mLooper->wake();
}

}

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
bool needWake = mInboundQueue.isEmpty();
mInboundQueue.enqueueAtTail(entry);

top Created with Sketch.