Android Input底层机制

前序

一般情况下很多同学对于点击事件的认识都只存在于从 Activity 开始的,然后从 Window 中进行分发,或者有些人有过自定义View的经验可能会涉及事件拦截处理等,会用到 onTouchEventdispatchTouchEvetn 这几个方法,会处理屏幕滑动,Down事件,Up事件等,但仅停留于对于 View 层的了解。

自定义View的事件处理其实在整个Android输入系统中只能算是最上层的,但这些事件是怎么产生的,上层View是怎么获得到这些事件的呢?喜欢研究底层实现原理的我,今天就给大家讲讲。

输入系统

事件产生的底层主要是输入子系统,Android 中的输入设备有很多,例如屏幕,鼠标,键盘等都是输入设备,对于应用开发者,接触最多的也就是屏幕了。当输入设备可用时,Linux会在 /dev/input 中创建对应的设备节点。用户操作输入设备就会产生各种事件,这些事件的原始信息就会被 Linux内核中的输入子系统采集,原始信息由 Kernel space 的驱动层一直传递到设备结点。

Android提供了一些api可以让开发者在设备节点(dev/input/)中读取内核写入的事件。

InputManagerService

IMS 的工作就是监听 /dev/input 下所有设备的节点,当有数据时就会进行加工处理,并交给中间层的 WMS 派发给合适的 Window。后面简称为IMS。

WindowManagerService

Android的WMS主要是负责窗口管理,窗口的启动、添加、删除,管理窗口大小、层级等,WMS与IMS息息相关。IMS将底层封装好的事件通过Socket pair传递给WMS,WMS通过绑定好的InputChannel找到对应的窗口进行app层的事件分发,这时候就到我们熟悉的Activity的事件分发流程了。

下面借张图,整体流程画的非常详细:

今天我们主要讲底层硬件驱动产生的事件的获取及分发流程,至于硬件驱动层就不展开介绍了。而这里面最重要的就是IMS相关的流程,下面开始补足知识盲区。

IMS的初始化

IMS的构造

和WMS一样,IMS 也是在 SystemServer 中创建的 ,也是 startOtherServers 方法中。

less 复制代码
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
        ...
        inputManager = new InputManagerService(context);
        wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
                new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
        ...
        inputManager.start();
    }

主要做了三件事:

  • 调用了IMS的构造方法
  • 将IMS作为参数传递给WMS
  • 调用了IMS的启动方法start

IMS 构造函数

scss 复制代码
public InputManagerService(Context context) {
        this.mContext = context;
        //注释1
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
        mStaticAssociations = loadStaticInputPortAssociations();
        mUseDevInputEventForAudioJack =
                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
        Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
                + mUseDevInputEventForAudioJack);
        //注释2
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
    }

我们重点关注1创建了一个在android.display 线程的InputManagerHandler,所以后面这个handler处理的消息都是执行在android.display 线程中,android.display 线程是系统共享的单例前台线程,可以用做一些低延时显示的相关操作,WMS 的创建也是在 android.display 中创建的。

注释2 的话,调用了nativeInit 方法,进入native侧创建了 NativeInputManager ,并将该对象指针返回给了 java层,方便后续调用处理。

scss 复制代码
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
                        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == nullptr) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }
    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

接下来看下NativeInputManager的创建

scss 复制代码
NativeInputManager::NativeInputManager(jobject contextObj,
                                           jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
        JNIEnv* env = jniEnv();
        mContextObj = env->NewGlobalRef(contextObj);//注释1
        mServiceObj = env->NewGlobalRef(serviceObj);//注释2
        ...
        sp<EventHub> eventHub = new EventHub();//注释3
        mInputManager = new InputManager(eventHub, this, this);//4
    }
  • 注释1:将java层的Context上下文保存在mContextObj中
  • 注释2:将java层传递下来的InputManagerService对象保存在mServiceObj中。
  • 注释3:创建了一个EventHub,EventHub 通过 Linux 内核的 Notify 与 Epoll 机制监听设备节点,通过 EventHub 的 getEvent 函数读取设备节点的增删事件和原始输入事件。
  • 最后创建了InputManager 对象
scss 复制代码
InputManager::InputManager(
            const sp<InputReaderPolicyInterface>& readerPolicy,
            const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
        //注释1
        mDispatcher = createInputDispatcher(dispatcherPolicy);
        mClassifier = new InputClassifier(mDispatcher);
        //注释2
        mReader = createInputReader(readerPolicy, mClassifier);
    }
    sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
        const sp<InputListenerInterface>& listener) {
        return new InputReader(std::make_unique<EventHub>(), policy, listener);
    }

其内部会创建InputDispatcher和InputReader。

  • InputDispatcher,该类主要用于对原始事件的分发,传递给 WMS。
  • InputReader,会不断的读取 EventHub 中的原始信息进行加工并交给 InputDispatcher ,InputDispatcher 中保存了 Window 的信息(WMS 会将窗口的信息实时更新到 InputDispatcher 中),可以将事件信息派发到合适的窗口,InputReader 和 InputDispatcher 都是耗时操作,会在单独线程中执行。

IMS构造主要是创建了NativeInputManager的native对象并返回给java层,将java层的IMS对象保存在native层的mServiceObj中,创建用于读取设备节点事件的EventHub对象。在InputManager构造中,创建了一个InputDispatcher和InputReader对象,以及用于读取事件的InputReaderThread线程和分发事件的InputDispatcherThread线程。

IMS启动

在 IMS 创建完成之后,就会调用他的 start 方法进行启动

csharp 复制代码
public void start() {
        nativeStart(mPtr);
}

调用native的start并传入native 层 NativeInputManager 对象的指针,会调native的InputManager的start方法,该方法内部分别会IputDispatcher和InputReader的start方法。

scss 复制代码
status_t InputManager::start() {
        //注释1
        status_t result = mDispatcher->start();
        if (result) {
            ALOGE("Could not start InputDispatcher thread due to error %d.", result);
            return result;
        }
        //注释2
        result = mReader->start();
        if (result) {
            ALOGE("Could not start InputReader due to error %d.", result);
            mDispatcher->stop();
            return result;
        }
        return OK;
    }
  • 调用IputDispatcher 的 start 方法,用于对事件进行分发
  • 调用InputReader 的 start 方法,用于从 EventHub 中获取原始事件进行处理。

InputReader 读取事件

kotlin 复制代码
status_t InputReader::start() {
        if (mThread) {
            return ALREADY_EXISTS;
        }
        mThread = std::make_unique<InputThread>(
                "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
        return OK;
    }

InputReader中实例化了一个InputThread ,该类继承了 Thread,其内部有一个循环,当线程运行时,会调用 threadloop 函数,如果返回了 true 并且没有调用 requestExit 函数,就会循环调用 threadloop 函数,线程中真正执行的是loopOnce

scss 复制代码
void InputReader::loopOnce() {
        ......
        //1
        size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
        { // acquire lock
            if (count) {
                //2
                processEventsLocked(mEventBuffer, count);
            }
        } // release lock
        ......
        //3  
        mQueuedListener->flush();
    }

主要做三件事:

  • 调用 EventHub 的 getEvents 函数来获取设备节点的信息到 mEventBuffer 中,事件信息主要有两种,一种是设备的增删事件(设备事件),一种是原始的输入事件,注:方法内部在没有输入事件的时候会进行休眠,并不会持续占用CPU​
  • 对 mEventBuffer 中的输入事件信息进行加工处理,加工处理后的事件会交给 InputDispatcher 来处理
  • 如果无事件就调用flush方法,一次性将所有事件给InputDispatcher处理

我们继续看下怎么对事件进行加工处理的,

c 复制代码
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
        for (const RawEvent* rawEvent = rawEvents; count;) {
            int32_t type = rawEvent->type;
            size_t batchSize = 1;
            //注释1
            if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
                int32_t deviceId = rawEvent->deviceId;
                while (batchSize < count) {
                    if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||
                            rawEvent[batchSize].deviceId != deviceId) {
                        break;
                    }
                    batchSize += 1;
                }
                //注释2
                processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
            } else {
                switch (rawEvent->type) {//3
                    case EventHubInterface::DEVICE_ADDED:
                        addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                        break;
                    case EventHubInterface::DEVICE_REMOVED:
                        removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                        break;
                    case EventHubInterface::FINISHED_DEVICE_SCAN:
                        handleConfigurationChangedLocked(rawEvent->when);
                        break;
                    default:
                        ALOG_ASSERT(false); // can't happen
                        break;
                }
            }
            count -= batchSize;
            rawEvent += batchSize;
        }
    }
    void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {
        ...
        std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
        ...
        mDevices.emplace(eventHubId, device);
        ...
    }

所有事件用 RawEvent 封装,但对原始原始事件和设备事件分开处理事件和设备事件分开处理,其中设备事件有三种类型,分别是节点的添加,删除、扫描等操作,原始事件主要是交给processEventsForDeviceLocked处理,先来看下processEventsForDeviceLocked逻辑。

c 复制代码
void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
                                                   size_t count) {
        //注释1
        auto deviceIt = mDevices.find(eventHubId);
        if (deviceIt == mDevices.end()) {
            ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);
            return;
        }
        //注释2
        std::shared_ptr<InputDevice>& device = deviceIt->second;
        if (device->isIgnored()) {
            // ALOGD("Discarding event for ignored deviceId %d.", deviceId);
            return;
        }
        device->process(rawEvents, count);
    }

mDevices 中存的key是eventHub,而 value就是设备InputDevice,查到后进行处理

ini 复制代码
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
        for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
            if (mDropUntilNextSync) {
                if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                    mDropUntilNextSync = false;
                } else {
              .....
                }
            } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
                //缓冲区溢出
                ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
                mDropUntilNextSync = true;
                reset(rawEvent->when);
            } else {
                for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) {
                        mapper.process(rawEvent);
            });
            }
            --count;
        }
    }
    inline void for_each_mapper_in_subdevice(int32_t eventHubDevice,
                                             std::function<void(InputMapper&)> f)
 {
        auto deviceIt = mDevices.find(eventHubDevice);
        if (deviceIt != mDevices.end()) {
            auto& devicePair = deviceIt->second;
            auto& mappers = devicePair.second;
            for (auto& mapperPtr : mappers) {
                f(*mapperPtr);
            }
        }
  

最终是使用不同的InputMapper进行处理,原始输入事件的类型很多,因此 InputMapper 有很多子类用于加工不同的原始输入事件,例如 TouchInputMapper 用于处理触摸输入事件,KeyboardInputMapper 处理键盘输入事件等。以触摸事件为例,看下TouchInputMapper执行。

rust 复制代码
void TouchInputMapper::process(const RawEvent* rawEvent) {
        mCursorButtonAccumulator.process(rawEvent); //处理鼠标事件,type == EV_KEY 进行处理
        mCursorScrollAccumulator.process(rawEvent); //处理鼠标滚轮事件,type == EV_REL 进行处理
        mTouchButtonAccumulator.process(rawEvent);  //处理屏幕触摸事件,type == EV_KEY 进行处理
        if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
            sync(rawEvent->when, rawEvent->readTime);
        }
    }
arduino 复制代码
void TouchInputMapper::processRawTouches(bool timeout) {
    ....
    cookAndDispatch(when, readTime);
    ....   
}
scss 复制代码
 void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
        BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
        BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
        int32_t metaState = getContext()->getGlobalMetaState();
        int32_t buttonState = mCurrentCookedState.buttonState;
        // Dispatch pointer up events.
        while (!upIdBits.isEmpty()) {
            .....
            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0,
                    isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState, buttonState, 0,
                    mLastCookedState.cookedPointerData.pointerProperties,
                    mLastCookedState.cookedPointerData.pointerCoords,
                    mLastCookedState.cookedPointerData.idToIndex, dispatchedIdBits, upId,
                    mOrientedXPrecision, mOrientedYPrecision, mDownTime);
            dispatchedIdBits.clearBit(upId);
            mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId);
        }
        // Dispatch move events if any of the remaining pointers moved from their old locations.
        // Although applications receive new locations as part of individual pointer up
        // events, they do not generally handle them except when presented in a move event.
        if (moveNeeded && !moveIdBits.isEmpty()) {
            ...
            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0,
                    metaState, buttonState, 0,
                    mCurrentCookedState.cookedPointerData.pointerProperties,
                    mCurrentCookedState.cookedPointerData.pointerCoords,
                    mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, -1,
                    mOrientedXPrecision, mOrientedYPrecision, mDownTime);
        }
        // Dispatch pointer down events using the new pointer locations.
        while (!downIdBits.isEmpty()) {
             ...
            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN,
                    0, 0, metaState, buttonState, 0,
                    mCurrentCookedState.cookedPointerData.pointerProperties,
                    mCurrentCookedState.cookedPointerData.pointerCoords,
                    mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits,
                    downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime);
        }
    }

会根据记录的上一次触摸位置,根据事件类型做相应的分发。事件类型有Up,Down,Move等,最终调用

arduino 复制代码
 void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
                                          uint32_t source, int32_t action, int32_t actionButton,
                                          int32_t flags, int32_t metaState, int32_t buttonState,
                                          int32_t edgeFlags, const PointerProperties* properties,
                                          const PointerCoords* coords, const uint32_t* idToIndex,
                                          BitSet32 idBits, int32_t changedId, float xPrecision,
                                          float yPrecision, nsecs_t downTime) {
        //最终形成的 NoteifyMotionArgs 对象
        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
                policyFlags, action, actionButton, flags, metaState, buttonState,
                MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
                pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,downTime, std::move(frames));
        //回调到 InputDispatcher 的 notifyMotion 方法中
        getListener()->notifyMotion(&args);
    }
c 复制代码
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
        .......
        bool needWake;
        {
            // Just enqueue a new motion event.
            std::unique_ptr<MotionEntry> newEntry =
                    std::make_unique<MotionEntry>(args->id, args->eventTime, args->deviceId,
                                              ......);
            needWake = enqueueInboundEventLocked(std::move(newEntry));
            mLock.unlock();
        } // release lock
        if (needWake) {
            mLooper->wake();
        }
    }
        bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {
        bool needWake = mInboundQueue.empty();
            //将事件添加到 minboundQueue 中
        mInboundQueue.push_back(std::move(newEntry));
        EventEntry& entry = *(mInboundQueue.back());
        traceInboundQueueLengthLocked();
        ...
        return needWake;
    }

最终调用enqueueInboundEventLocked 方法并将MotionEntry添加到队列,然后决定是否唤醒InputDispatcher线程​。

获取事件的流程至此结束,​我们总结下:

  • InputReader 只是调用 EventHub 的 getEvent 获取了原始事件,获取到事件后,就会根据原始事件找到对应的 InputDevice(设备对象)。
  • 在 InputDevice 中,根据事件获取到对应的 InputMapper 用于加工事件。InputMapper 子类分别对应了不同的事件类型,例如触摸事件,多点触摸事件,按键事件等。
  • 最终事件被加工结束后都会通过 getListener 回调掉 InputDispatcher 中对应的两个方法,比如触摸事件被封装成了 MotionEntry 对象。最后调用 enqueueInboundEventLocked 方法,决定是否需要唤醒 InputDispatcher 线程

总结

由于篇幅过长,为避免带来阅读不适,事件分发后面再​讲。感谢关注,感谢一键三连​。

微信公众号首发,第一时间获取新知识请关注,感谢支持,欢迎大家关注、转发、评论。

相关推荐
Dingdangr5 小时前
Android中的Intent的作用
android
技术无疆5 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP5 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
Jouzzy12 小时前
【Android安全】Ubuntu 16.04安装GDB和GEF
android·ubuntu·gdb
极客先躯12 小时前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
Good_tea_h15 小时前
Android中的单例模式
android·单例模式
计算机源码社20 小时前
分享一个基于微信小程序的居家养老服务小程序 养老服务预约安卓app uniapp(源码、调试、LW、开题、PPT)
android·微信小程序·uni-app·毕业设计项目·毕业设计源码·计算机课程设计·计算机毕业设计开题
丶白泽20 小时前
重修设计模式-结构型-门面模式
android
晨春计21 小时前
【git】
android·linux·git
标标大人1 天前
c语言中的局部跳转以及全局跳转
android·c语言·开发语言