Android 输入系统介绍

news2025/2/27 7:21:47

文章目录

  • 一、目的
  • 二、环境
  • 三、相关概念
    • 3.1 输入设备
    • 3.2 UEVENT机制
    • 3.3 JNI
    • 3.4 EPOLL机制
    • 3.5 INotify
  • 四、详细设计
    • 4.1 结构图
    • 4.2 代码结构
    • 4.3 InputManagerService模块
      • 4.3.1 IMS服务入口
      • 4.3.2 IMS初始化
      • 4.3.3 IMS启动
      • 4.3.4 IMS消息监听
    • 4.4 NativeInputManager模块
      • 4.4.1 nativeInit初始化
      • 4.4.2 nativeStart启动
    • 4.5 Inputflinger模块
      • 4.6.2 EventHub获取事件队列
      • 4.6.3 Input事件加工
      • 4.6.4 事件发布
    • 4.7 Inputdispatcher模块
      • 4.7.1 Input事件上报
      • 4.7.2 启动InputDispatcher线程
    • 4.8 WindowManagerService模块
      • 4.8.2 WindowManagerService.addWindow()
      • 4.8.3 WindowInputEventReceiver
  • 五、Input设备节点介绍
    • 5.1 常见触摸事件类型
    • 5.2 getevent
    • 5.3 sendevent
  • 六、参考资料

一、目的

        最近接触到了一个问题:耳机插入事件的由来,走读了下IMS输入系统服务的源码。同时,IMS输入系统服务在Android的开发过程中,也经常出现,有必要了解下相关原理。

  1. 学习下IMS输入系统的源码设计,了解该模块承担的业务职责,熟悉Android结构
  2. 了解Android屏幕点击事件、物理按键事件的分发规则

二、环境

  1. 版本:Android 11
  2. 平台:高通 QCM2290

三、相关概念

3.1 输入设备

        常见的输入设备有鼠标、键盘、触摸屏等,用户通过输入设备与系统进行交互。

3.2 UEVENT机制

        “uevent” 是 Linux 系统中的一种事件通知机制,用于向用户空间发送有关内核和设备状态变化的通知。这种机制通常用于设备驱动程序、热插拔事件以及设备状态变化等场景,以便用户空间应用程序能够在这些事件发生时做出相应的响应。

3.3 JNI

        JNI,全称Java Native Interface,是Java编程语言的一种编程框架,用于实现Java代码与其他编程语言(如C、C++)进行交互的接口。JNI允许Java程序调用原生代码(native code),即由其他编程语言编写的代码,并且允许原生代码调用Java代码。通过JNI,Java程序可以访问底层系统功能、使用硬件设备、调用第三方库等。

3.4 EPOLL机制

        监听多个描述符的可读/可写状态。等待返回时携带了可读的描述符

3.5 INotify

        Linux 内核所提供的一种文件系统变化通知机制。可以监控文件系统的变化,如文件新建、删除、读写等

四、详细设计

通过屏幕的触摸事件,来分析IMS系统,相关如下

4.1 结构图

4.2 代码结构

层级模块描述源码编译产物
FrameworkInputManagerServicexxxframeworks/base/services/core/java/out/target/product/qssi/system/framework/services.jar
NativeNativeInputManagerxxxframeworks/base/services/core/jni/out/target/product/qssi/system/lib64/libandroid_servers.so
NativeInputflingerxxxframeworks/native/services/inputflinger/out/target/product/qssi/system/lib64/libinputflinger.so
NativeInputreaderxxxframeworks/native/services/inputflinger/readerout/target/product/qssi/system/lib64/libinputreader.so
NativeInputdispatcherxxxframeworks/native/services/inputflinger/dispatcher/(静态库)out/soong/.intermediates/frameworks/native/services/inputflinger/dispatcher/libinputdispatcher/android_arm64_armv8-a_static/libinputdispatcher.a
NativeNativeInputEventReceiverxxxframeworks/base/core/jni/out/target/product/qssi/system/lib64/libandroid_runtime
NativeInputChannelxxxframeworks/native/libs/input/out/target/product/qssi/system/lib64/libinput.so

4.3 InputManagerService模块

        InputManagerService是Android框架层一个非核心服务,主要是提供一个IMS输入系统启动的入口,同时对应用层提供业务相关接口。

4.3.1 IMS服务入口

        Android设备开机后,会启动system_server进程,InputManagerService服务(以下简称IMS)在该进程被唤起。

@frameworks\base\services\java\com\android\server\SystemServer.java
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
    ...
    t.traceBegin("StartInputManagerService");
    inputManager = new InputManagerService(context);//新建IMS实例
    t.traceEnd();
    ...
    t.traceBegin("StartInputManager");
    inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());//设置窗体事件监听
    inputManager.start();//启动IMS服务
    t.traceEnd();
    ...
}

4.3.2 IMS初始化

        此处做一些IMS相关的初始化操作,会调用nativeInit方法,获取一个NativeInputManager对象,类似于一个句柄。

@frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
private static native long nativeInit(InputManagerService service,
        Context context, MessageQueue messageQueue);
public InputManagerService(Context context) {
    ...
    mStaticAssociations = loadStaticInputPortAssociations();
    mUseDevInputEventForAudioJack =
            context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
    Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
            + mUseDevInputEventForAudioJack);
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
    ...
}

4.3.3 IMS启动

        InputManagerService通过start方法启动,会调用nativeStart方法,该方法为Native方法

@frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
private static native void nativeStart(long ptr);
public void start() {
    Slog.i(TAG, "Starting input manager");
    nativeStart(mPtr);

    // Add ourself to the Watchdog monitors.
    Watchdog.getInstance().addMonitor(this);
    ...
}

4.3.4 IMS消息监听

        该方法为Native的回调方法,用于上报IMS事件,如耳机插入事件等。

@frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
// Native callback.
private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
    ...
    if ((switchMask & SW_LID_BIT) != 0) {
        final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0);
        mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
    }

    if ((switchMask & SW_CAMERA_LENS_COVER_BIT) != 0) {
        final boolean lensCovered = ((switchValues & SW_CAMERA_LENS_COVER_BIT) != 0);
        mWindowManagerCallbacks.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered);
    }

    if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) {
        mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues,
                    switchMask);
    }
    ...
}

4.4 NativeInputManager模块

        该模块为JNI模块,主要处理Java方法与c++方法映射关系,即IMS服务与InputFlinger模块的通信桥梁。

4.4.1 nativeInit初始化

(1)新建一个NativeInputManager对象,并将该对象返回给java层

@\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    ...
    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

(2)创建InputManager管理类,主要用于管理Input事件分发、事件读取行为

@\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();
    ...
    mInputManager = new InputManager(this, this);
    defaultServiceManager()->addService(String16("inputflinger"),
            mInputManager, false);
}

4.4.2 nativeStart启动

获取上一个阶段创建NativeInputManager对象,并引用start启动该模块

@\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.");
    }
}

4.5 Inputflinger模块

input事件的管理类,数据传递类,也是输入系统native层核心的模块。
ps: 根据字典里的定义,flinger是指出轨的人。在SurfaceFlinger的例子中,它把可视数据扔给surface AudioFlinger把音频数据扔给适当的接收者。它们只是“可爱”的词… 😃

### 4.5.1 启动事件管理服务         启动两个核心的阻塞线程,一个是事件分发线程,一个是事件读取线程。 ```c++ @frameworks\native\services\inputflinger\InputManager.cpp status_t InputManager::start() { status_t result = mDispatcher->start();//启动事件分发服务 if (result) { ALOGE("Could not start InputDispatcher thread due to error %d.", result); return result; }
result = mReader->start();//启动事件读取服务
if (result) {
    ALOGE("Could not start InputReader due to error %d.", result);

    mDispatcher->stop();
    return result;
}

return OK;

}


## 4.6 Inputreader模块
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;事件读取服务,读取驱动上报事件

### 4.6.1 启动InputReader线程
(1)创建一个InputThread线程
```c
@frameworks\native\services\inputflinger\reader\InputReader.cpp
status_t InputReader::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    mThread = std::make_unique<InputThread>(
            "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
    return OK;
}

(2)InputThread线程的loop循环队列(线程和loop的关系)

@frameworks\native\services\inputflinger\reader\InputReader.cpp
void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    ...
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);//step 1. 通过EventHub抽取事件列表
    { // acquire lock
        ...
        if (count) {
            processEventsLocked(mEventBuffer, count);// step 2. 对事件进行加工处理
        }
        ...
    } // release lock
    ...
    mQueuedListener->flush();//step 3. 事件发布
}

4.6.2 EventHub获取事件队列

        EventHub:事件集线器,它将全部的输入事件通过一个接口getEvents(),将从多个输入设备节点中读取的事件交给InputReader,是输入系统最底层的一个组件。
(1)EventHub的构造函数
        它通过INotifyEpoll机制建立起了对设备节点增删事件以及可读状态的监听。同时,EventHub创建了一个名为wakeFds的匿名管道,因为InputReader在运行getEvents()时会因无事件而导致其线程堵塞在epoll_wait()的调用里,然而有时希望能够立马唤醒InputReader线程使其处理一些请求。

@frameworks\native\services\inputflinger\reader\EventHub.cpp
static const char* DEVICE_PATH = "/dev/input";
EventHub::EventHub(void)
      : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
        mNextDeviceId(1),
        mControllerNumbers(),
        mOpeningDevices(nullptr),
        mClosingDevices(nullptr),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false),
        mNeedToScanDevices(true),
        mPendingEventCount(0),
        mPendingEventIndex(0),
        mPendingINotify(false) {
    ensureProcessCanBlockSuspend();

    mEpollFd = epoll_create1(EPOLL_CLOEXEC);//创建一个epoll对象,用来监听设备节点是否有事件
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    mINotifyFd = inotify_init();//创建一个inotify对象,用来监听设备节点的增删事件
    mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    ...
    struct epoll_event eventItem = {};
    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = mINotifyFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);//将mINotifyFd注册进epoll对象中
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);

    int wakeFds[2];
    result = pipe(wakeFds);//创建一个匿名管道,用于唤醒EventHub,避免无事件引起阻塞
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);

    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
    ...
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);//将管道读取端加入epoll对象中
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
                        errno);
}

mEpollFd监听如下几个事件:设备节点的增加、删除、修改;匿名管道,避免无事件阻塞

(2)RawEvent结构体
        mEventBuffer用于描述原始输入事件,其类型为RawEvent,相关结构体如下:

@frameworks\native\services\inputflinger\reader\include\EventHub.h
/*
 * A raw event as retrieved from the EventHub.
 */
struct RawEvent {
    nsecs_t when;//事件时间戳
    int32_t deviceId;//产生事件的设备ID
    int32_t type;//事件类型
    int32_t code;//事件编码
    int32_t value;//事件值
};

(3)EventHub->getEvents事件,
        getEvents()是事件处理的核心方法,其通过EPOLL机制和INOTIFY,从多个设备节点读取事件。

@frameworks\native\services\inputflinger\reader\EventHub.cpp
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ...
    for (;;) {
        ...
        if (mNeedToScanDevices) {//Step 1.扫描设备
            mNeedToScanDevices = false;
            scanDevicesLocked();
            mNeedToSendFinishedDeviceScan = true;
        }
        ...
        // Grab the next input event.
        bool deviceChanged = false;
        while (mPendingEventIndex < mPendingEventCount) { //Step 2.处理未被InputReader取走的输入事件与设备事件
            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
            ...
            // This must be an input event
            if (eventItem.events & EPOLLIN) {
                int32_t readSize =
                        read(device->fd, readBuffer, sizeof(struct input_event) * capacity);//Step 3.读取底层上报事件
                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                    ...
                } else {
                    int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;

                    size_t count = size_t(readSize) / sizeof(struct input_event);
                    for (size_t i = 0; i < count; i++) {//构建需要上报的事件
                        struct input_event& iev = readBuffer[i];
                        event->when = processEventTimestamp(iev);
                        event->deviceId = deviceId;
                        event->type = iev.type;
                        event->code = iev.code;
                        event->value = iev.value;
                        event += 1;//将event指针移动到下一个可用于填充事件的RawEvent对象
                        capacity -= 1;
                    }
                    ...
                }
            } 
            ...
        }
        ...
        mLock.unlock(); // release lock before poll
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);//Step 4.阻塞,等待事件各种类型消息
        mLock.lock(); // reacquire lock after poll
        ...
    }
    // All done, return the number of events we read.
    return event - buffer;
}

Step 1. 扫描设备,会获取input/dev/下的所有设备,并将各个设备注册到epoll线程池里,监听各个设备的消息状态;
Step 2. 处理未被InputReader取走的输入事件与设备事件,一般情况下有事件上报时,epoll_wait会读取到mPendingEventItems值,即mPendingEventCount值,即会进入该流程;
Step 3. 读取底层上报事件,根据上报的fd设备,读取对应的设备节点。即可以获取到上报的事件内容。如下为屏幕点击对应的上报事件:

Step 4. 通过epoll机制,阻塞当前进程,等待设备节点变更,事件上报。

4.6.3 Input事件加工

        主要是将底层RawEvent事件,进一步加工,将Event事件注入到mArgsQueue队列的过程。
(1)Input事件加工

@frameworks\native\services\inputflinger\reader\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) {
            ...
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);//输入事件
        } else {
            switch (rawEvent->type) {
                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;
    }

}

(2)Input事件推送
该流程业务代码比较冗长,做了层层封装,如下为方法调用栈:
InputReader.processEventsLocked() -> InputReader.processEventsForDeviceLocked() -> InputDevice.process() -> MultiTouchInputMapper.process() -> TouchInputMapper.process()->TouchInputMapper.sync() -> TouchInputMapper.processRawTouches() -> TouchInputMapper.cookAndDispatch() -> TouchInputMapper.dispatchTouches() -> TouchInputMapper.dispatchMotion() -> QueuedInputListener -> notifyMotion()
最终可以看到事件最终会传递到mArgsQueue容器内。

@frameworks\native\services\inputflinger\InputListener.cpp
std::vector<NotifyArgs*> mArgsQueue;
void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
    traceEvent(__func__, args->id);
    mArgsQueue.push_back(new NotifyMotionArgs(*args));
}

4.6.4 事件发布

(1)当事件加工完成后,会引用flush()方法,将事件发布出去

@frameworks\native\services\inputflinger\InputListener.cpp
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();
}

(2)由上一节可知,屏幕点击事件对应的args为NotifyMotionArgs

@frameworks\native\services\inputflinger\InputListener.cpp
void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyMotion(this);
}

(3)大家可以自己去追溯下源码,该listener接口的实现类是InputDispatcher。至此,事件将进入下一阶段——事件分发。

@frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
    ...
}

4.7 Inputdispatcher模块

        事件分发服务,将底层读到的事件,分发到上层

4.7.1 Input事件上报

        至此,我们知道InputDispatch会启动一个阻塞线程,等待底层事件上报;而通过InputReader的分析,我们知道底层事件响应,最终会通知InputDispatch模块的notifyMotion()方法

@frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
    ...
    { // acquire lock
        mLock.lock();
        ...
        // Just enqueue a new motion event.
        MotionEntry* newEntry =
                new MotionEntry(args->id, args->eventTime, args->deviceId, args->source,
                                args->displayId, policyFlags, args->action, args->actionButton,
                                args->flags, args->metaState, args->buttonState,
                                args->classification, args->edgeFlags, args->xPrecision,
                                args->yPrecision, args->xCursorPosition, args->yCursorPosition,
                                args->downTime, args->pointerCount, args->pointerProperties,
                                args->pointerCoords, 0, 0);

        needWake = enqueueInboundEventLocked(newEntry);//构建新的MotionEvent事件
        mLock.unlock();
    } // release lock

    if (needWake) {
        mLooper->wake();//唤醒InputDispatch线程,进行分发
    }
}

4.7.2 启动InputDispatcher线程

(1)创建一个InputDispatcher线程

@frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
status_t InputDispatcher::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    mThread = std::make_unique<InputThread>(
            "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
    return OK;
}

(2)InputThread线程的loop队列

@frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();

        // Run a dispatch loop if there are no pending commands.
        // The dispatch loop might enqueue commands to run afterwards.
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);//事件分发
        }
        ...
    } // release lock

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);//堵塞,等待唤醒
}

(3)事件分发过程
事件的分发过程也比较冗长,此处不具体分析过程,其业务堆栈如下,即事件分发最终会下发到publishMotionEvent。
InputDispatcher.dispatchOnceInnerLocked() -> InputDispatcher.dispatchMotionLocked() -> InputDispatcher.dispatchEventLocked() -> InputDispatcher.prepareDispatchCycleLocked() -> InputDispatcher.enqueueDispatchEntriesLocked() -> InputDispatcher.startDispatchCycleLocked() -> InputPublisher.publishMotionEvent()

@frameworks\native\libs\input\InputTransport.cpp
status_t InputPublisher::publishMotionEvent(
        uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,
        std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
        int32_t edgeFlags, int32_t metaState, int32_t buttonState,
        MotionClassification classification, float xScale, float yScale, float xOffset,
        float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
        float yCursorPosition, nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
        const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) {
    ...
    InputMessage msg;
    msg.header.type = InputMessage::Type::MOTION;
    msg.body.motion.seq = seq;
    msg.body.motion.eventId = eventId;
    ...
    return mChannel->sendMessage(&msg);
}

4.8 WindowManagerService模块

### 4.8.1 ViewRootImpl阶段         InputDispatcher通过InputChannel将事件发送到目标窗口的进程了。那么目标窗口是如何接收传递事件呢? (1)Activity创建窗口相关阶段介绍 **attach阶段:** 一个Activity 创建了一个PhoneWindow对象 ,PhoneWindow通过setWindowManager() 创建了WindowManagerImpl 。 即Activity 对应一个PhoneWindow,并得到了一个WindowManager(WindowManagerImpl,Window创建的)。 **onCreate阶段:** 创建了DecorView ,并将 activity的布局添加到DecorView中 。 **onResume阶段:** 创建了ViewRootImpl,通过setView()最终由Session进入system_server进程。最终执行addWindow添加窗口到WMS。

(2)ViewRootImpl.setView()

@frameworks\base\core\java\android\view\ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                ...
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();//创建inputChannel对象
                }
                try {
                    ...
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);//通过session跨进程调用WMS的addWindow方法给inputChannel赋值
                    setFrame(mTmpFrame);
                }
                ...
                if (inputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());//创建mInputEventReceiver对象,用于App侧接收Input事件
                }
                ...
            }
        }
    }

4.8.2 WindowManagerService.addWindow()

(1)openInputChannel():生成一对inputChannel,并返回一个对象给App端。
Session.addToDisplayAsUser() -> WindowManagerService.addWindow() -> EmbeddedWindow.openInputChannel()

@frameworks\base\services\core\java\com\android\server\wm\EmbeddedWindowController.java
InputChannel openInputChannel() {
    final String name = getName();

    final InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//InputChannel底层通过一对socket进行通信
    mInputChannel = inputChannels[0];
    final InputChannel clientChannel = inputChannels[1];
    mWmService.mInputManager.registerInputChannel(mInputChannel);//将一个inputChannel对象注册到Input的Native端
    ...
    return clientChannel;//返回一个inputChannel对象给App端
}

(2)openInputChannelPair():创建一对通过socket通信的inputChannel对象。
InputChannel.openInputChannelPair() -> InputChannel.nativeOpenInputChannelPair() -> android_view_InputChannel.android_view_InputChannel_nativeOpenInputChannelPair() -> InputTransport.openInputChannelPair()

@frameworks\native\libs\input\InputTransport.cpp
status_t InputChannel::openInputChannelPair(const std::string& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
        ALOGE("channel '%s' ~ Could not create socket pair.  errno=%d",
                name.c_str(), errno);
        outServerChannel.clear();
        outClientChannel.clear();
        return result;
    }

    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));

    sp<IBinder> token = new BBinder();

    std::string serverChannelName = name + " (server)";
    android::base::unique_fd serverFd(sockets[0]);
    outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);//server端

    std::string clientChannelName = name + " (client)";
    android::base::unique_fd clientFd(sockets[1]);
    outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);//client端
    return OK;
}

4.8.3 WindowInputEventReceiver

        app进程和system_server进程通过socket通信,底层捕获的事件最终通过inputChannel模块来实现,再由app端的WindowInputEventReceiver去接收,最后把事件分发到目标View上。
(1)WindowInputEventReceiver构造函数
注册一个事件接收器,WindowInputEventReceiver的父类是InputEventReceiver

@frameworks\base\core\jni\android_view_InputEventReceiver.cpp
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
    ...
    mInputChannel = inputChannel;
    mMessageQueue = looper.getQueue();
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
            inputChannel, mMessageQueue);//初始化操作

    mCloseGuard.open("dispose");
}

// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchInputEvent(int seq, InputEvent event) {//native层事件回调方法
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event);//事件分发到各个目标View上
}

(2)nativeInit
由上可知,在添加窗口时,WMS会针对于每个窗口设置一对InputChannel对象,分为client端和server端,其中server端在system_server进程,client端在app进程。我们需要去监听client端,以期能够捕获server端的事件消息。

@frameworks\base\core\jni\android_view_InputEventReceiver.cpp
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) {
    ...
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    status_t status = receiver->initialize();//初始化
    ...
    receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
    return reinterpret_cast<jlong>(receiver.get());
}

status_t NativeInputEventReceiver::initialize() {
    setFdEvents(ALOOPER_EVENT_INPUT);
    return OK;
}

void NativeInputEventReceiver::setFdEvents(int events) {
    if (mFdEvents != events) {
        mFdEvents = events;
        int fd = mInputConsumer.getChannel()->getFd();//此fd为WMS创建的InputChannel的client端
        if (events) {
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);//注册监听
        } else {
            mMessageQueue->getLooper()->removeFd(fd);//移除监听
        }
    }
}

(3)handleEvent
当server端写入事件时,client端的looper就能被唤醒,会调用handleEvent函数(当fd可读时,会调用LooperCallback的handleEvent,而NativeInputEventReceiver继承自LooperCallback,所以这里会调用NativeInputEventReceiver的handleEvent函数,线程和looper的关系此处不展开)

@frameworks\base\core\jni\android_view_InputEventReceiver.cpp
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    ...
    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);//处理事件
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
    ...
    return 1;
}

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    ...
    for (;;) {
        ...
        if (!skipCallbacks) {
            ...
            if (inputEventObj) {
                env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);//事件消息回调java层
                if (env->ExceptionCheck()) {
                    ALOGE("Exception dispatching input event.");
                    skipCallbacks = true;
                }
                env->DeleteLocalRef(inputEventObj);
            }
        }
        ...
    }
}

五、Input设备节点介绍

5.1 常见触摸事件类型

事件类型事件名称事件编码事件定义
EV_SYN同步事件0004 or 0005代表一个事件开始(不必要)
EV_SYN同步事件SYN_REPORT代表一个事件结束(必要的)
EV_ABS绝对坐标的事件ABS_MT_SLOT本质代表着不同的手指,他的value代表手指id
EV_ABS绝对坐标的事件ABS_MT_TRACKING_ID类协议特有的,每个slot会和一个ID相对应,一个非负数表示一次接触,ffffffff表示一次接触结束,即手指抬起。无论在接触的类型相对应的slot发生改变,驱动都应该通过改变这个值来使这个slot失效,并且下一次触摸的ID值会是这次的值加1
EV_ABS绝对坐标的事件ABS_MT_POSITION_X相对于屏幕中心的x坐标
EV_ABS绝对坐标的事件ABS_MT_POSITION_Y相对于屏幕中心的y坐标
EV_ABS绝对坐标的事件ABS_MT_TOUCH_MAJOR接触部分的长轴长度,相当于椭圆的长轴
EV_ABS绝对坐标的事件ABS_MT_TOUCH_MINOR接触部分的短轴长度,相当于椭圆的短轴
EV_ABS绝对坐标的事件ABS_MT_PRESSURE代表按下压力,有的设备不一定有
EV_KEY按键事件BTN_TOUCH触碰按键,其值是DOWN或者UP
EV_KEY按键事件BTN_TOOL_FINGER按键的是finger,其值是DOWN或者UP

5.2 getevent

adb shell getevent -lt

5.3 sendevent

模拟按压音量键+

//通过getevent指令,获取音量按键+的事件码
bengal:/ # getevent
add device 1: /dev/input/event4
  name:     "bengal-scubaidp-snd-card Button Jack"
add device 2: /dev/input/event3
  name:     "bengal-scubaidp-snd-card Headset Jack"
add device 3: /dev/input/event0
  name:     "qpnp_pon"
add device 4: /dev/input/event1
  name:     "gpio-keys"
add device 5: /dev/input/event2
  name:     "sitronix_ts_i2c"
/dev/input/event1: 0001 0073 00000001
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0001 0073 00000000
/dev/input/event1: 0000 0000 00000000

//通过sendevent模拟音量键+的事件
130|bengal:/ # sendevent /dev/input/event1 1 115 1
bengal:/ # sendevent /dev/input/event1 0 0 0
bengal:/ # sendevent /dev/input/event1 1 115 0
bengal:/ # sendevent /dev/input/event1 0 0 0
bengal:/ #

ps:getevent获取到的事件码为16进制,sendevent输入的值为10进制,需要注意下!!!

六、参考资料

https://liuwangshu.blog.csdn.net/article/details/84883156
https://liuwangshu.blog.csdn.net/article/details/86771746
https://www.cnblogs.com/brucemengbm/p/7072395.html
事件分发介绍:
https://www.cnblogs.com/fanglongxiang/p/14091511.html
InputChannel介绍:
https://blog.csdn.net/ztisen/article/details/130188132
GetEvent指令介绍:
https://blog.csdn.net/Gary1_Liu/article/details/124675608

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1373002.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从0到1实战微服务架构之Nacos下载安装

目录 一、前言 二、Nacos概述 三、Nacos架构 3.1 Open API 3.2 Config Service 3.3 Naming Service 3.4 Nacos Core 3.5 Consistency Protocol 四、Nacos部署实践 4.1 Nacos下载 4.2 Nacos部署 五、总结 一、前言 Nacos是一个开源的、易于使用的、功能丰富的平台&a…

Java项目:02 基于ssm超市订单管理系统

项目介绍 基于ssm超市订单管理系统 环境&#xff1a;jdk1.8&#xff0c;mysql5.7&#xff0c;tomcat8.5&#xff0c;maven3.6 软件&#xff1a;IDEA 功能&#xff1a;超市后台管理系统&#xff0c;有订单管理&#xff0c;供应商管理&#xff0c;用户管理&#xff0c;密码修改&…

开源了,免费使用GPT4(Windows/Linux/Mac 一键启动脚本)

开源了&#xff0c;免费使用GPT4&#xff08;Windows一键启动脚本&#xff09; 大家好&#xff0c;我打算每日花1小时来写一篇文章&#xff0c;这一小时包括文章主题思考和实现&#xff0c;连续日更几天&#xff0c;看看能不能被官方推荐。&#xff08;帮我点点赞哦&#xff5…

im6ull学习总结(三-4)freetype显示单个字体

矢量字体引入 使用点阵字库显示英文字母、汉字时&#xff0c;大小固定&#xff0c;如果放大缩小则会模糊甚至有锯齿出现&#xff0c;为了解决这个问题&#xff0c;引用矢量字体。 矢量字体形成分三步&#xff1a; 第1步 确定关键点&#xff0c; 第2步 使用数学曲线&#xff08…

Springboot的配置文件详解:从入门到精通,解读配置文件的奇妙世界

目录 1、前言 2、介绍 2.1 Springboot配置文件的作用 2.2 Springboot支持的配置文件类型 2.3 Springboot配置文件的加载顺序 3、YAML配置文件 3.1 YAML基本语法介绍 3.2 YAML中的基本数据类型 3.3 YAML中的复合数据类型 3.4 YAML中的配置属性 3.5 YAML中的多环境配置…

Apache ActiveMQ RCE CNVD-2023-69477 CVE-2023-46604

漏洞简介 Apache ActiveMQ官方发布新版本&#xff0c;修复了一个远程代码执行漏洞&#xff0c;攻击者可构造恶意请求通过Apache ActiveMQ的61616端口发送恶意数据导致远程代码执行&#xff0c;从而完全控制Apache ActiveMQ服务器。 影响版本 Apache ActiveMQ 5.18.0 before …

【从零开始学习微服务 | 第一篇】什么是微服务

目录 前言&#xff1a; 架构风格&#xff1a; 单体架构&#xff1a; 分布式架构&#xff1a; 微服务&#xff1a; 总结&#xff1a; 前言&#xff1a; 在当今快速发展的软件开发领域&#xff0c;构建大型应用程序已经成为一项巨大的挑战。传统的单体应用架构往往难以满足…

蓝桥杯练习题(三)

&#x1f4d1;前言 本文主要是【算法】——蓝桥杯练习题&#xff08;三&#xff09;的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 …

【Scala】——面向对象

1 Scala 包 1.1 包风格 Scala 有两种包的管理风格。 第一种 Java 的包管理风格相同&#xff0c;每个源文件一个包&#xff08;包 名和源文件所在路径不要求必须一致&#xff09;&#xff0c;包名用“.”进行分隔以表示包的层级关系&#xff0c;如 com.atguigu.scala。另一种风…

Laravel 使用rdkafka_laravel详细教程(实操避坑)

一、选择rdkafka 首先要看版本兼容问题&#xff0c;我的是Laravel5.6&#xff0c;PHP是7.3.13&#xff0c;所以需要下载兼容此的rdkafka&#xff0c;去 Packagist 搜索 kafka &#xff0c;我用的是 Packagist选择里面0.10.5版本&#xff0c; 二、安装rdkafka 在 Laravel 项目…

P4学习(一) 环境搭建

系列文章目录 第一章 P4学习入门之虚拟机环境搭建 文章目录 系列文章目录前言一、P4是什么&#xff1f;二、搭建步骤1.下载虚拟机镜像2.虚拟机管理软件载入镜像2.1 找到你镜像的所在位置2.2 打开VMware Workstation2.3 载入镜像 3.检验环境是否配置成功 P4 的真机环境搭建 前言…

计算机基础面试题 |21.精选计算机基础面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

OCP NVME SSD规范解读-6.标准日志要求-1

4.8 Log Page Requirements章节在NVMe规范中主要涵盖了设备应支持的日志页面&#xff08;Log Pages&#xff09;的要求。日志页面是存储控制器用于报告内部状态、性能统计和其他关键信息的结构化数据区域&#xff0c;它们对系统管理和故障诊断至关重要。 本文&#xff0c;我们…

【LeetCode】组合两个表(mysql)

题目 编写解决方案&#xff0c;报告 Person 表中每个人的姓、名、城市和州。如果 personId 的地址不在 Address 表中&#xff0c;则报告为 null 。 以 任意顺序 返回结果表。 结果格式如下所示。 答 select firstName ,lastName,city,state from Person left join Address …

【JaveWeb教程】(17) MySQL数据库开发之 MySQL简介、安装、数据类型、SQL通用语法 详细代码示例讲解

目录 前言1. MySQL概述1.1 安装1.1.1 版本1.1.2 安装1.1.3 连接1.1.4 企业使用方式(了解) 1.2 数据模型1.3 SQL简介1.3.1 SQL通用语法1.3.2 分类 前言 在我们讲解SpringBootWeb基础知识(请求响应案例)的时候&#xff0c;我们讲到在web开发中&#xff0c;为了应用程序职责单一&…

基于深度学习的老照片修复系统

技术栈 深度学习 pytorch tensorflow python 卷积神经 神经网络 照片修复 vue 老照片修复 扫描褪色 残损照片或胶片 调整暗调/高光以改善面效果 修正曝光斑痕 背景&#xff1a; 随着时间的流逝&#xff0c;许多老照片可能会褪色、损坏或曝光不当。这些老照片记录了宝贵的回忆…

[足式机器人]Part2 Dr. CAN学习笔记-Advanced控制理论 Ch04-3Phase Portrait相图,相轨迹

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-Advanced控制理论 Ch04-3Phase Portrait相图&#xff0c;相轨迹 1. 1-D2. 2-D3. General Form4. Summary 1. 1-D 2. 2-D 3. General Form 4. Summary

牛刀小试 - C++ 实现2048(可存档)

参考文档 借助了这位大佬的开发思路&#xff0c; 开发过程中学到了很多 C语言实现《2048游戏》 技术点&#xff1a; system调整控制台大小的问题 unsigned and 符号位 C对齐输出&#xff08;左对齐和右对齐&#xff09; C goto语句详解 完整代码 /********************…

报错解决方法——http404(Spring MVC)

一.检查静态资源是否加载成功 成功的标志就是在项目跑起来之后再target文件夹中的classes文件夹中可以找到自己写的配置文件。 1.查看resources文件夹是否被识别为资源文件夹 如图所示&#xff0c;文件夹图标右下角有三条杠代表被识别为资源文件 2.在pox.xml文件中插入如下…

打造VR数字乡村文旅新品牌,VR全景技术助力乡村振兴

新年伊始&#xff0c;各地乡村特色产业都在蓬勃发展&#xff0c;让冬日里的乡村重新焕发了新的活力。并且在这个冬季&#xff0c;各地还依托生态资源优势&#xff0c;打造智慧乡村文旅新品牌&#xff0c;激活乡村消费活力&#xff0c;例如有些乡村利用空心村&#xff0c;打造多…