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 线程

总结

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

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

相关推荐
编程洪同学2 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端
氤氲息4 小时前
Android 底部tab,使用recycleview实现
android
Clockwiseee4 小时前
PHP之伪协议
android·开发语言·php
小林爱4 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
小何开发6 小时前
Android Studio 安装教程
android·ide·android studio
开发者阿伟6 小时前
Android Jetpack LiveData源码解析
android·android jetpack
weixin_438150996 小时前
广州大彩串口屏安卓/linux触摸屏四路CVBS输入实现同时显示!
android·单片机
CheungChunChiu7 小时前
Android10 rk3399 以太网接入流程分析
android·framework·以太网·eth·net·netd
木头没有瓜7 小时前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
键盘侠0077 小时前
springboot 上传图片 转存成webp
android·spring boot·okhttp