input系统之InputReader

news2024/11/14 5:24:02

1.InputReader启动

InputReader和InputDispatcher是IMS中的两个重要的线程,InputReader主要负责从Eventhub获取事件,然后将事件进行处理,并将封装好的EventEntry事件交给InputDispatcher的去进行分发;InputDispatcher主要负责将输入事件分发到对应的窗口。

这两个线程都是在IMS启动时创建并启动的,这里主要分析InputReader是如何工作的,代码基于Android 14:

IMS启动时会调用start方法去启动InputReader线程:

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;
}

创建一个名称为InputReader的线程,然后主要做了两件事情:执行loopOnce()方法、唤醒EventHub,主要看下loopOnce方法:

在这个线程中执行loopOnce()方法,用来读取输入设备生成的事件

frameworks/native/services/inputflinger/reader/InputReader.cpp

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    // Copy some state so that we can access it outside the lock later.
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    std::list<NotifyArgs> notifyArgs;
    { // acquire lock
        std::scoped_lock _l(mLock);//申请mLock锁,确保线程安全

        oldGeneration = mGeneration;
        timeoutMillis = -1;

        auto changes = mConfigurationChangesToRefresh;//InputReader的各种配置(输入设备的增删、信息)是否有变化
        if (changes.any()) {//如果配置变化,更新超时时间和配置
            mConfigurationChangesToRefresh.clear();
            timeoutMillis = 0;
            refreshConfigurationLocked(changes);
        } else if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
        }
    } // release lock

    std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis);//1.从EventHub中获取输入事件,getEvents方法会阻塞直到超时或者有事件到达

    { // acquire lock
        std::scoped_lock _l(mLock);
        mReaderIsAliveCondition.notify_all();

        if (!events.empty()) {//如果从EventHub中获取到了事件
            notifyArgs += processEventsLocked(events.data(), events.size());//2.processEventsLocked方法处理事件
        }

        if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            if (now >= mNextTimeout) {
                if (debugRawEvents()) {
                    ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
                }
                mNextTimeout = LLONG_MAX;
                notifyArgs += timeoutExpiredLocked(now);//3.处理超时事件
            }
        }

        if (oldGeneration != mGeneration) {//输入设备是否发生变化
            inputDevicesChanged = true;
            inputDevices = getInputDevicesLocked();
            notifyArgs.emplace_back(
                    NotifyInputDevicesChangedArgs{mContext.getNextId(), inputDevices});
        }
    } // release lock

    // Send out a message that the describes the changed input devices.
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    // Notify the policy of the start of every new stylus gesture outside the lock.
    for (const auto& args : notifyArgs) {
        const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args);
        if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) {
            mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime);
        }
    }

    notifyAll(std::move(notifyArgs));

    // Flush queued events out to the listener.
    // This must happen outside of the lock because the listener could potentially call
    // back into the InputReader's methods, such as getScanCodeState, or become blocked
    // on another thread similarly waiting to acquire the InputReader lock thereby
    // resulting in a deadlock.  This situation is actually quite plausible because the
    // listener is actually the input dispatcher, which calls into the window manager,
    // which occasionally calls into the input reader.
    mQueuedListener.flush();//4.将事件交给InputDispatcher
}

首先是从从EventHub.getEvents方法中获取输入事件,然后调用processEventsLocked()来继续处理输入事件,最后调用 mQueuedListener.flush(.将事件交给InputDispatcher分发。

先来看下EventHub是什么。

2.EventHub

EventHub可以通过getEvents()方法读取的输入事件交给InputReader,它是基于INotify与Epoll机制工作的,先来介绍下INotify与Epoll

2.1 INotify

INotify是Linux系统提供的可以用来监听文件系统变化的一种机制,它可以监控文件系统的变化,如文件的新建、删除、读写等。

如果有新的设备插入,那么在/dev/input/目录下会生成event*文件,INotify可以用来监听/dev/input/是否有设备文件的增删。

Inotify的主要方法有三个:

(1)inotify_init:用来创建inotify描述符。

(2)inotify_add_watch(参数1,参数2,参数3):将一个或者多个事件添加监听,参数1表示描述符、参数2表示监听路径、参数3表示监听事件(如创建、删除等) 。

(3)read():完成事件添加后,当被监听的事件发生时(比如:对应路径下发生创建或者删除文件操作时)会将相应的事件信息写入到描述符中,此时可以通过read()函数从inotify描述符中将事件信息读取出来。

Inotify并不是通过回调的方法通知事件发生,那么如何确认读取事件的时机呢?这就需要Linux的另外一个机制了:Epoll

2.2 Epoll

无论是硬件设备产生输入事件,还是出现设备节点的创建、删除(inotify监听),都不是随时随地发生的,为了避免资源的浪费,Epoll机制可以很好的监听输入事件的产生。

Epoll是 Linux 内核提供的一种高效的 I/O 事件通知机制,可以高效地等待多个文件描述符的事件并处理它们,适用于处理大量并发连接的应用程序。它负责监听是否有可读事件出现,即是否有输入事件产生。

Epoll的主要方法有三个:

(1)epoll_create(int size):创建一个epoll对象的描述符,之后对epoll的操作均使用这个描述符完成。size参数表示可以监听的描述符的最大数量。
(2)epoll_ctl (int epfd, int op,int fd, struct epoll_event *event):用于管理注册事件的函数。这个函数可以增加/删除/修改事件的注册。
(3)int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout):用于等待事件的到来。会使调用者陷入等待状态,直到其注册的事件之一发生之后才会返回。

INotify与Epoll的这些主要方法是在EventHub.cpp的构造方法里进行使用的

frameworks/native/services/inputflinger/reader/EventHub.cpp

static const char* DEVICE_INPUT_PATH = "/dev/input";


EventHub::EventHub(void)
      ......

    mEpollFd = epoll_create1(EPOLL_CLOEXEC);//创建epoll对象的描述符,监听设备节点是否出现事件
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    mINotifyFd = inotify_init1(IN_CLOEXEC);创建inotify对象的描述符,用于监听/dev/input/设备节点的变化
    LOG_ALWAYS_FATAL_IF(mINotifyFd < 0, "Could not create inotify instance: %s", strerror(errno));

    if (std::filesystem::exists(DEVICE_INPUT_PATH, errorCode)) {
        addDeviceInputInotify();//inotify监听/dev/input目录下设备节点的删除和创建.
    } 
    .....
    struct epoll_event eventItem = {};//创建epoll结构体
    eventItem.events = EPOLLIN | EPOLLWAKEUP;//epoll监听可读
    eventItem.data.fd = mINotifyFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);//添加事件监听
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);

    int wakeFds[2];//创建管道,将读端交给Epoll,写端交给InputReader,用来唤醒Epoll,避免阻塞在epoll_wait里
    result = pipe2(wakeFds, O_CLOEXEC);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);

    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];

    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
                        errno);

    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
                        errno);

    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);//将管道读取端的描述符注册到epoll中,用于监听读取端的可读事件,当写入端有事件写入时,读取端就有数据可以读,epoll_wait有返回值,从而唤醒InputReader,避免一直阻塞
}

void EventHub::addDeviceInputInotify() {
    mDeviceInputWd = inotify_add_watch(mINotifyFd, DEVICE_INPUT_PATH, IN_DELETE | IN_CREATE);//监听/dev/input目录下设备节点的删除和创建,当/dev/input/下的设备节点发生创建与删除操作时,会将相应的事件信息写入到inotifyFd所描述的inotify对象中
    LOG_ALWAYS_FATAL_IF(mDeviceInputWd < 0, "Could not register INotify for %s: %s",
                        DEVICE_INPUT_PATH, strerror(errno));
}

3.getEvents

接着来继续看getEvents()方法是如何从EventHub中获取事件的

frameworks/native/services/inputflinger/reader/EventHub.cpp
std::vector<RawEvent> EventHub::getEvents(int timeoutMillis) {
    std::scoped_lock _l(mLock);//加锁,确保线程安全

    std::array<input_event, EVENT_BUFFER_SIZE> readBuffer;//存储原始输入事件的buffer

    std::vector<RawEvent> events;//获取到的事件
    bool awoken = false;//是否唤醒线程来处理事件
    for (;;) {//循环体,如果有可用事件,将事件放入到events中并返回;如果没有可用事件,则进入epoll_wait()等待事件的到来,epoll_wait()返回后会重新循环将可用将新事件放入events 
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

        // Reopen input devices if needed.
        if (mNeedToReopenDevices) {//是否需要重新打开设备,InputReader的构造方法里会将这个值设置为true
            mNeedToReopenDevices = false;

            ALOGI("Reopening all input devices due to a configuration change.");

            closeAllDevicesLocked();//关闭所有设备
            mNeedToScanDevices = true;//是否扫描设备标识
            break; // return to the caller before we actually rescan
        }

        // Report any devices that had last been added/removed.
        for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {//遍历mClosingDevices,为已卸载的设备生成DEVICE_REMOVED事件
            std::unique_ptr<Device> device = std::move(*it);
            ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.c_str());
            const int32_t deviceId = (device->id == mBuiltInKeyboardId)
                    ? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID
                    : device->id;
            events.push_back({//向events队列的尾部加入DEVICE_REMOVED数据
                    .when = now,
                    .deviceId = deviceId,
                    .type = DEVICE_REMOVED,
            });
            it = mClosingDevices.erase(it);
            mNeedToSendFinishedDeviceScan = true;
            if (events.size() == EVENT_BUFFER_SIZE) {
                break;
            }
        }

        if (mNeedToScanDevices) {//扫描设备
            mNeedToScanDevices = false;
            scanDevicesLocked();
            mNeedToSendFinishedDeviceScan = true;
        }

        while (!mOpeningDevices.empty()) {
            std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());
            mOpeningDevices.pop_back();
            ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str());
            const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
            events.push_back({//向events队列的尾部加入DEVICE_ADDED数据
                    .when = now,
                    .deviceId = deviceId,
                    .type = DEVICE_ADDED,
            });

            // Try to find a matching video device by comparing device names
            for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end();
                 it++) {
                std::unique_ptr<TouchVideoDevice>& videoDevice = *it;
                if (tryAddVideoDeviceLocked(*device, videoDevice)) {
                    // videoDevice was transferred to 'device'
                    it = mUnattachedVideoDevices.erase(it);
                    break;
                }
            }

            auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device));
            if (!inserted) {
                ALOGW("Device id %d exists, replaced.", device->id);
            }
            mNeedToSendFinishedDeviceScan = true;
            if (events.size() == EVENT_BUFFER_SIZE) {
                break;
            }
        }

        if (mNeedToSendFinishedDeviceScan) {//设备扫描结束
            mNeedToSendFinishedDeviceScan = false;
            events.push_back({//向events队列的尾部加入FINISHED_DEVICE_SCAN数据
                    .when = now,
                    .type = FINISHED_DEVICE_SCAN,
            });
            if (events.size() == EVENT_BUFFER_SIZE) {
                break;
            }
        }

        // Grab the next input event.
        bool deviceChanged = false;
        while (mPendingEventIndex < mPendingEventCount) {//mPendingEventIndex当前处理的事件,mPendingEventCount需要处理的事件数量
            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];//从mPendingEventItems中读取事件
            if (eventItem.data.fd == mINotifyFd) {//如果获取到的epoll_event是mINotifyFd监听的事件,即设备节点删减事件
                if (eventItem.events & EPOLLIN) {//事件可读
                    mPendingINotify = true;// 标记INotify事件待处理
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
                }
                continue;// 继续处理下一条epoll_event事件,直到不是mINotifyFd为止
            }

            if (eventItem.data.fd == mWakeReadPipeFd) {//如果获取到的是注册监听mWakeReadPipeFd事件,表示有事件可以读取
                if (eventItem.events & EPOLLIN) {//事件可读
                    ALOGV("awoken after wake()");
                    awoken = true;//设置awoken为true
                    char wakeReadBuffer[16];
                    ssize_t nRead;
                    do {//循环调用read方法读取管道中的数据
                        nRead = read(mWakeReadPipeFd, wakeReadBuffer, sizeof(wakeReadBuffer));
                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(wakeReadBuffer));
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
                          eventItem.events);
                }
                continue;// 继续处理下一条epoll_event事件,直到不是mWakeReadPipeFd为止
            }

            Device* device = getDeviceByFdLocked(eventItem.data.fd);//根据事件的文件描述符获取设备device
            if (device == nullptr) {//设备为空
                ALOGE("Received unexpected epoll event 0x%08x for unknown fd %d.", eventItem.events,
                      eventItem.data.fd);
                ALOG_ASSERT(!DEBUG);
                continue;//继续遍历循环
            }
            if (device->videoDevice && eventItem.data.fd == device->videoDevice->getFd()) {//TouchVideoDevice相关,这里不做分析
                if (eventItem.events & EPOLLIN) {
                    size_t numFrames = device->videoDevice->readAndQueueFrames();
                    if (numFrames == 0) {
                        ALOGE("Received epoll event for video device %s, but could not read frame",
                              device->videoDevice->getName().c_str());
                    }
                } else if (eventItem.events & EPOLLHUP) {
                    // TODO(b/121395353) - consider adding EPOLLRDHUP
                    ALOGI("Removing video device %s due to epoll hang-up event.",
                          device->videoDevice->getName().c_str());
                    unregisterVideoDeviceFromEpollLocked(*device->videoDevice);
                    device->videoDevice = nullptr;
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events,
                          device->videoDevice->getName().c_str());
                    ALOG_ASSERT(!DEBUG);
                }
                continue;
            }
            // This must be an input event
            if (eventItem.events & EPOLLIN) {//输入事件,表示当前事件是输入设备节点产生的输入事件
                int32_t readSize =
                        read(device->fd, readBuffer.data(),
                             sizeof(decltype(readBuffer)::value_type) * readBuffer.size());
                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                    // Device was removed before INotify noticed.
                    ALOGW("could not get event, removed? (fd: %d size: %" PRId32
                          " capacity: %zu errno: %d)\n",
                          device->fd, readSize, readBuffer.size(), errno);
                    deviceChanged = true;
                    closeDeviceLocked(*device);
                } else if (readSize < 0) {
                    if (errno != EAGAIN && errno != EINTR) {
                        ALOGW("could not get event (errno=%d)", errno);
                    }
                } else if ((readSize % sizeof(struct input_event)) != 0) {
                    ALOGE("could not get event (wrong size: %d)", readSize);
                } else {
                    const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;

                    const size_t count = size_t(readSize) / sizeof(struct input_event);
                    for (size_t i = 0; i < count; i++) {
                        struct input_event& iev = readBuffer[i];
                        events.push_back({//将读取到输入事件存入events中
                                .when = processEventTimestamp(iev),
                                .readTime = systemTime(SYSTEM_TIME_MONOTONIC),
                                .deviceId = deviceId,
                                .type = iev.type,
                                .code = iev.code,
                                .value = iev.value,
                        });
                    }
                    if (events.size() >= EVENT_BUFFER_SIZE) {
                        // The result buffer is full.  Reset the pending event index
                        // so we will try to read the device again on the next iteration.
                        mPendingEventIndex -= 1;
                        break;
                    }
                }
            } else if (eventItem.events & EPOLLHUP) {//挂起事件
                ALOGI("Removing device %s due to epoll hang-up event.",
                      device->identifier.name.c_str());
                deviceChanged = true;
                closeDeviceLocked(*device);//关闭设备节点
            } else {
                ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events,
                      device->identifier.name.c_str());
            }
        }

        // readNotify() will modify the list of devices so this must be done after
        // processing all other events to ensure that we read all remaining events
        // before closing the devices.
        if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {//mINotifyFd有数据可读,说明设备节点发生了增删操作
            mPendingINotify = false;
            const auto res = readNotifyLocked();//读取mINotifyFd中的事件,同时对输入设备进行相应的加载与卸载操作
            if (!res.ok()) {
                ALOGW("Failed to read from inotify: %s", res.error().message().c_str());
            }
            deviceChanged = true;
        }

        // Report added or removed devices immediately.
        if (deviceChanged) {
            continue;// 设备节点增删操作发生时,则重新执行循环体
        }

        // Return now if we have collected any events or if we were explicitly awoken.
        if (!events.empty() || awoken) {//如果收集到事件或者要求唤醒InputReader,退出循环。
            break;
        }

        // Poll for events.
        // When a device driver has pending (unread) events, it acquires
        // a kernel wake lock.  Once the last pending event has been read, the device
        // driver will release the kernel wake lock, but the epoll will hold the wakelock,
        // since we are using EPOLLWAKEUP. The wakelock is released by the epoll when epoll_wait
        // is called again for the same fd that produced the event.
        // Thus the system can only sleep if there are no events pending or
        // currently being processed.
        //
        // The timeout is advisory only.  If the device is asleep, it will not wake just to
        // service the timeout.
        mPendingEventIndex = 0;

        mLock.unlock(); // release lock before poll

        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);//如果getevents没能获取事件,等待新事件到来

        mLock.lock(); // reacquire lock after poll

        if (pollResult == 0) {
            // Timed out.
            mPendingEventCount = 0;
            break;
        }

        if (pollResult < 0) {
            // An error occurred.
            mPendingEventCount = 0;

            // Sleep after errors to avoid locking up the system.
            // Hopefully the error is transient.
            if (errno != EINTR) {
                ALOGW("poll failed (errno=%d)\n", errno);
                usleep(100000);
            }
        } else {
            // Some events occurred.
            mPendingEventCount = size_t(pollResult);//epoll_wait等到新的事件,重新循环,对新事件进行处理
        }
    }

    // All done, return the number of events we read.
    return events;//返回读取到的事件给InputReader
}

getEvents方法用来读取设备增删事件与原始输入事件,并将它们封装为RawEvent结构体,RawEvent结构体在 Android 系统中用于表示原始事件数据,定义如下:

frameworks/native/services/inputflinger/reader/include/EventHub.h
struct RawEvent {
    // Time when the event happened
    nsecs_t when;//事件发生的时间
    // Time when the event was read by EventHub. Only populated for input events.
    // For other events (device added/removed/etc), this value is undefined and should not be read.
    nsecs_t readTime;//EventHub 读取事件的时间
    int32_t deviceId;//发生事件的设备id
    int32_t type;//事件的类型,比如按键事件、触摸事件
    int32_t code;//事件的code
    int32_t value;//事件的值
};

方法的重点是一个死循环,如果有可用事件,将事件放入到events中并返回;如果没有可用事件,则进入epoll_wait()等待事件的到来,epoll_wait()返回后会重新循环将可用将新事件放入events。

(1)getEvents方法首先判断是否需要重新打开设备,如果需要重新打开设备,后面会对设备进行扫描。mNeedToReopenDevices变量在InputReader的构造方法里会将被设置为true,一般是第一次用来记录设备信息;

(2)接着分别遍历mClosingDevices、mOpeningDevices来检查是否有设备添加/删除/扫描,用以生成DEVICE_REMOVED、DEVICE_ADDED、FINISHED_DEVICE_SCAN事件,push_back()方法向events队列的尾部加入DEVICE_REMOVED、DEVICE_ADDED、FINISHED_DEVICE_SCAN数据;

(3)while循环处理待处理的epoll_event事件,mPendingEventCount是需要处理的事件数量,只要mPendingEventCount有值(表示有输入事件产生),它就会一直循环。从mPendingEventItems中取出事件,对每一个eventItem事件进行读取:

如果获取到的epoll_event是mINotifyFd监听的事件并且可读,即设备节点删减事件,将mPendingINotify设置为true,标记INotify事件待处理,然后继续处理下一条epoll_event事件,直到不是mINotifyFd为止;

如果获取到的epoll_event是mWakeReadPipeFd事件,表示管道有事件可以读取,read方法循环调用read方法读取管道中的数据,直到mWakeReadPipeFd事件读取完为止;

如果获取到的rpoll_event是输入事件,即当前事件是输入设备节点产生的输入事件,那么将事件添加到events中;

如果mPendingINotify为true(前面读取mINotifyFd监听的事件会设置这个标识为true),说明设备节点发生了增删操作,对mINotifyFd里的事件进行读取;

(4)如果getEvents()方法获取到事件,或者需要唤醒InputReader,那么退出循环,结束本次调用,以便InputReader可以立刻处理这些事件;

(5)如果getevents没能获取事件,epoll_wait等待新事件到来,并将结果存储在mPendingEventItems里

(6)返回读取到的事件events给InputReader处理

4.processEventsLocked

getEvents获取到输入事件之后,processEventsLocked()继续处理输入事件:

frameworks/native/services/inputflinger/reader/InputReader.cpp

std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {//参数表示读取的事件数据、事件个数
    std::list<NotifyArgs> out;//用于存储处理后的事件
    for (const RawEvent* rawEvent = rawEvents; count;) {//遍历每一个RawEvent事件
        int32_t type = rawEvent->type;//当前事件类型
        size_t batchSize = 1;
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {//输入设备产生的事件
            int32_t deviceId = rawEvent->deviceId;//设备节点Id
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||
                    rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }
            if (debugRawEvents()) {
                ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
            }
            out += 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;
    }
    return out;
}

processEventsLocked方法会遍历getEvents()方法获取到的事件,如果是输入事件,那么调用processEventsForDeviceLocked()方法继续处理,并将处理好的事件结果添加到out列表中,这个out列表后面会交给InputDisapatch处理;

如果发生设备的增加、删除、扫描事件,那么调用addDeviceLocked、removeDeviceLocked、handleConfigurationChangedLocked方法分别处理。

这里主要看下processEventsForDeviceLocked()方法是如何处理设备输入事件的

frameworks/native/services/inputflinger/reader/InputReader.cpp

std::list<NotifyArgs> InputReader::processEventsForDeviceLocked(int32_t eventHubId,
                                                                const RawEvent* rawEvents,
                                                                size_t count) {
    auto deviceIt = mDevices.find(eventHubId);//根据eventHubId获取deviceIt
    if (deviceIt == mDevices.end()) {//未找到设备节点
        ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);
        return {};
    }

    std::shared_ptr<InputDevice>& device = deviceIt->second;
    if (device->isIgnored()) {
        // ALOGD("Discarding event for ignored deviceId %d.", deviceId);
        return {};
    }

    return device->process(rawEvents, count);//继续调用InputDevice.cpp的process方法
}

调用InputDevice.cpp里的process方法

frameworks/native/services/inputflinger/reader/InputDevice.cpp

std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t count) {
    // Process all of the events in order for each mapper.
    // We cannot simply ask each mapper to process them in bulk because mappers may
    // have side-effects that must be interleaved.  For example, joystick movement events and
    // gamepad button presses are handled by different mappers but they should be dispatched
    // in the order received.
    std::list<NotifyArgs> out;
    for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {//遍历每个事件
        if (debugRawEvents()) {
            const auto [type, code, value] =
                    InputEventLookup::getLinuxEvdevLabel(rawEvent->type, rawEvent->code,
                                                         rawEvent->value);
            ALOGD("Input event: eventHubDevice=%d type=%s code=%s value=%s when=%" PRId64,
                  rawEvent->deviceId, type.c_str(), code.c_str(), value.c_str(), rawEvent->when);
        }

        if (mDropUntilNextSync) {
            if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                out += reset(rawEvent->when);
                mDropUntilNextSync = false;
                ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun.");
            } else {
                ALOGD_IF(debugRawEvents(),
                         "Dropped input event while waiting for next input sync.");
            }
        } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
            ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
            mDropUntilNextSync = true;
        } else {
            for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {
                out += mapper.process(rawEvent);//重点:调用每个子设备的InputMapper.process方法处理事件
            });
        }
        --count;
    }
    postProcess(out);
    return out;
}

主要逻辑是调用每个子设备对应的InputMapper.process方法处理事件,通过 InputMapper可以将原始输入事件转换为处理过的输入数据,单个输入设备可以有多个关联的InputMapper。

InputMapper是一个抽象类,它有很多子类:

比如处理键盘输入事件的KeyboardInputMapper、比如处理触摸输入事件的TouchInputMapper、处理鼠标输入事件的MouseInputMapper、处理光标输入事件的CursorInputMapper。

这里来看下键盘输入事件KeyboardInputMapper的process方法:

frameworks/native/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
std::list<NotifyArgs> KeyboardInputMapper::process(const RawEvent* rawEvent) {
    std::list<NotifyArgs> out;
    mHidUsageAccumulator.process(*rawEvent);
    switch (rawEvent->type) {
        case EV_KEY: {//如果是键盘码
            int32_t scanCode = rawEvent->code;//获取scan code

            if (isSupportedScanCode(scanCode)) {//如果是支持的scan code
                out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0,
                                  scanCode, mHidUsageAccumulator.consumeCurrentHidUsage());//调用processKey方法处理按键
            }
            break;
        }
    }
    return out;
}

std::list<NotifyArgs> KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down,
                                                      int32_t scanCode, int32_t usageCode) {
    std::list<NotifyArgs> out;
    int32_t keyCode;
    int32_t keyMetaState;
    uint32_t policyFlags;
    int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM;

    if (getDeviceContext().mapKey(scanCode, usageCode, mMetaState, &keyCode, &keyMetaState,
                                  &policyFlags)) {//扫描码映射失败
        keyCode = AKEYCODE_UNKNOWN;
        keyMetaState = mMetaState;
        policyFlags = 0;
    }

    nsecs_t downTime = when;
    std::optional<size_t> keyDownIndex = findKeyDownIndex(scanCode);
    if (down) {//按键被按下
        // Rotate key codes according to orientation if needed.
        if (mParameters.orientationAware) {//如果设备支持方向感知,根据设备的方向旋转按键代码
            keyCode = rotateKeyCode(keyCode, getOrientation());
        }

        // Add key down.
        if (keyDownIndex) {
            // key repeat, be sure to use same keycode as before in case of rotation
            keyCode = mKeyDowns[*keyDownIndex].keyCode;
            downTime = mKeyDowns[*keyDownIndex].downTime;
            flags = mKeyDowns[*keyDownIndex].flags;
        } else {
            // key down
            if ((policyFlags & POLICY_FLAG_VIRTUAL) &&
                getContext()->shouldDropVirtualKey(when, keyCode, scanCode)) {
                return out;
            }
            if (policyFlags & POLICY_FLAG_GESTURE) {
                out += getDeviceContext().cancelTouch(when, readTime);
                flags |= AKEY_EVENT_FLAG_KEEP_TOUCH_MODE;
            }

            KeyDown keyDown;
            keyDown.keyCode = keyCode;
            keyDown.scanCode = scanCode;
            keyDown.downTime = when;
            keyDown.flags = flags;
            mKeyDowns.push_back(keyDown);
        }
        onKeyDownProcessed(downTime);
    } else {//按键up事件
        // Remove key down.
        if (keyDownIndex) {
            // key up, be sure to use same keycode as before in case of rotation
            keyCode = mKeyDowns[*keyDownIndex].keyCode;
            downTime = mKeyDowns[*keyDownIndex].downTime;
            flags = mKeyDowns[*keyDownIndex].flags;
            mKeyDowns.erase(mKeyDowns.begin() + *keyDownIndex);
        } else {
            // key was not actually down
            ALOGI("Dropping key up from device %s because the key was not down.  "
                  "keyCode=%d, scanCode=%d",
                  getDeviceName().c_str(), keyCode, scanCode);
            return out;
        }
    }

    if (updateMetaStateIfNeeded(keyCode, down)) {
        // If global meta state changed send it along with the key.
        // If it has not changed then we'll use what keymap gave us,
        // since key replacement logic might temporarily reset a few
        // meta bits for given key.
        keyMetaState = mMetaState;
    }

    // Any key down on an external keyboard should wake the device.
    // We don't do this for internal keyboards to prevent them from waking up in your pocket.
    // For internal keyboards and devices for which the default wake behavior is explicitly
    // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each
    // wake key individually.
    if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault &&
        !(mKeyboardType != AINPUT_KEYBOARD_TYPE_ALPHABETIC && isMediaKey(keyCode))) {
        policyFlags |= POLICY_FLAG_WAKE;
    }

    if (mParameters.handlesKeyRepeat) {
        policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
    }

    //创建一个 NotifyKeyArgs 对象,包含按键事件的详细信息,例如事件 ID、时间戳、设备信息、按键代码等,并将其添加到 out 列表中
    out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
                                   mSource, getDisplayId(), policyFlags,
                                   down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, flags,
                                   keyCode, scanCode, keyMetaState, downTime));
    return out;//返回一个 NotifyArgs 对象的列表,包含按键事件的详细信息
}

分别对按键的按下和抬起事件进行处理 ,将按键事件的详细信息,例如事件 ID、时间戳、设备信息、按键代码等封装成一个NotifyKeyArgs对象,并将其添加到 out 列表中返回。

从上面过程可以看出原始输入事件经过getEvents()之后被封装成RawEvent,经过processEventsLocked方法之后被封装成NotifyKeyArgs对象。

最后,mQueuedListener.flush()方法将封装好的事件传递给InputDispatcher。

5.flush()

frameworks/native/services/inputflinger/InputListener.cpp
void QueuedInputListener::flush() {
    for (const NotifyArgs& args : mArgsQueue) {
        mInnerListener.notify(args);//遍历每个事件,调用mInnerListener.notify()方法
    }
    mArgsQueue.clear();
}

这里的args就是上文各个mapper生成的NotifyKeyArgs。主要是调用InputListener.cpp里的notify方法

frameworks/native/services/inputflinger/InputListener.cpp
void InputListenerInterface::notify(const NotifyArgs& generalArgs) {
    Visitor v{
            [&](const NotifyInputDevicesChangedArgs& args) { notifyInputDevicesChanged(args); },
            [&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(args); },
            [&](const NotifyKeyArgs& args) { notifyKey(args); },
            [&](const NotifyMotionArgs& args) { notifyMotion(args); },
            [&](const NotifySwitchArgs& args) { notifySwitch(args); },
            [&](const NotifySensorArgs& args) { notifySensor(args); },
            [&](const NotifyVibratorStateArgs& args) { notifyVibratorState(args); },
            [&](const NotifyDeviceResetArgs& args) { notifyDeviceReset(args); },
            [&](const NotifyPointerCaptureChangedArgs& args) { notifyPointerCaptureChanged(args); },
    };
    std::visit(v, generalArgs);
}

根据传入的args类型,调用不同的方法,这里调用的是notifyKey(args)方法。

InputDispatcher继承了InputListenerInterface,所以这个方法实际上是在InputDispatcher里实现的

frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::notifyKey(const NotifyKeyArgs& args) {
    ALOGD_IF(debugInboundEventDetails(),
             "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64
             ", deviceId=%d, source=%s, displayId=%" PRId32
             "policyFlags=0x%x, action=%s, flags=0x%x, keyCode=%s, scanCode=0x%x, metaState=0x%x, "
             "downTime=%" PRId64,
             args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),
             args.displayId, args.policyFlags, KeyEvent::actionToString(args.action), args.flags,
             KeyEvent::getLabel(args.keyCode), args.scanCode, args.metaState, args.downTime);
    Result<void> keyCheck = validateKeyEvent(args.action);//检查是否是可用的keyevent
    if (!keyCheck.ok()) {
        LOG(ERROR) << "invalid key event: " << keyCheck.error();
        return;
    }

    uint32_t policyFlags = args.policyFlags;
    int32_t flags = args.flags;
    int32_t metaState = args.metaState;
    // InputDispatcher tracks and generates key repeats on behalf of
    // whatever notifies it, so repeatCount should always be set to 0
    constexpr int32_t repeatCount = 0;
    if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
        policyFlags |= POLICY_FLAG_VIRTUAL;
        flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
    }
    if (policyFlags & POLICY_FLAG_FUNCTION) {
        metaState |= AMETA_FUNCTION_ON;
    }

    policyFlags |= POLICY_FLAG_TRUSTED;

    int32_t keyCode = args.keyCode;
    KeyEvent event;
    event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC, args.action,
                     flags, keyCode, args.scanCode, metaState, repeatCount, args.downTime,
                     args.eventTime);//初始化event

    android::base::Timer t;
    mPolicy.interceptKeyBeforeQueueing(event, /*byref*/ policyFlags);//调用PhoneWindowManager里的interceptKeyBeforeQueueing方法
    if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
        ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms",
              std::to_string(t.duration().count()).c_str());
    }

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

        if (shouldSendKeyToInputFilterLocked(args)) {
            mLock.unlock();

            policyFlags |= POLICY_FLAG_FILTERED;
            if (!mPolicy.filterInputEvent(event, policyFlags)) {
                return; // event was consumed by the filter
            }

            mLock.lock();
        }

        std::unique_ptr<KeyEntry> newEntry =
                std::make_unique<KeyEntry>(args.id, /*injectionState=*/nullptr, args.eventTime,
                                           args.deviceId, args.source, args.displayId, policyFlags,
                                           args.action, flags, keyCode, args.scanCode, metaState,
                                           repeatCount, args.downTime);//封装成KeyEntry
        if (mTracer) {
            newEntry->traceTracker = mTracer->traceInboundEvent(*newEntry);
        }

        needWake = enqueueInboundEventLocked(std::move(newEntry));//入队
        mLock.unlock();
    } // release lock

    if (needWake) {
        mLooper->wake();//如果有必要,唤醒线程。
    }
}

这里有一个很熟悉的方法mPolicy.interceptKeyBeforeQueueing,最终调用的是PhoneWindowManager里的interceptKeyBeforeQueueing,在入队之前判断是否需要对事件进行拦截。

然后将事件封装成KeyEntry,接着调用enqueueInboundEventLocked将事件入队。

bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {
    bool needWake = mInboundQueue.empty();//mInboundQueue为空则需要唤醒
    mInboundQueue.push_back(std::move(newEntry));//将封装好的EventEntry事件加入mInboundQueue队列
    const EventEntry& entry = *(mInboundQueue.back());
    traceInboundQueueLengthLocked();

    switch (entry.type) {
        case EventEntry::Type::KEY: {
            LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0,
                                "Unexpected untrusted event.");

            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
            if (mTracer) {
                ensureEventTraced(keyEntry);
            }

            // If a new up event comes in, and the pending event with same key code has been asked
            // to try again later because of the policy. We have to reset the intercept key wake up
            // time for it may have been handled in the policy and could be dropped.
            if (keyEntry.action == AKEY_EVENT_ACTION_UP && mPendingEvent &&
                mPendingEvent->type == EventEntry::Type::KEY) {
                const KeyEntry& pendingKey = static_cast<const KeyEntry&>(*mPendingEvent);
                if (pendingKey.keyCode == keyEntry.keyCode &&
                    pendingKey.interceptKeyResult ==
                            KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
                    pendingKey.interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
                    pendingKey.interceptKeyWakeupTime = 0;
                    needWake = true;
                }
            }
            break;
        }

        case EventEntry::Type::MOTION: {
            LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0,
                                "Unexpected untrusted event.");
            const auto& motionEntry = static_cast<const MotionEntry&>(entry);
            if (mTracer) {
                ensureEventTraced(motionEntry);
            }
            if (shouldPruneInboundQueueLocked(motionEntry)) {
                mNextUnblockedEvent = mInboundQueue.back();
                needWake = true;
            }

            const bool isPointerDownEvent = motionEntry.action == AMOTION_EVENT_ACTION_DOWN &&
                    isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER);
            if (isPointerDownEvent && mKeyIsWaitingForEventsTimeout) {
                // Prevent waiting too long for unprocessed events: if we have a pending key event,
                // and some other events have not yet been processed, the dispatcher will wait for
                // these events to be processed before dispatching the key event. This is because
                // the unprocessed events may cause the focus to change (for example, by launching a
                // new window or tapping a different window). To prevent waiting too long, we force
                // the key to be sent to the currently focused window when a new tap comes in.
                ALOGD("Received a new pointer down event, stop waiting for events to process and "
                      "just send the pending key event to the currently focused window.");
                mKeyIsWaitingForEventsTimeout = now();
                needWake = true;
            }
            break;
        }
        case EventEntry::Type::FOCUS: {
            LOG_ALWAYS_FATAL("Focus events should be inserted using enqueueFocusEventLocked");
            break;
        }
        case EventEntry::Type::TOUCH_MODE_CHANGED:
        case EventEntry::Type::CONFIGURATION_CHANGED:
        case EventEntry::Type::DEVICE_RESET:
        case EventEntry::Type::SENSOR:
        case EventEntry::Type::POINTER_CAPTURE_CHANGED:
        case EventEntry::Type::DRAG: {
            // nothing to do
            break;
        }
    }

    return needWake;
}

主要是将封装好的EventEntry事件加入mInboundQueue队列。

6.总结

可以看出InputReader处理事件的流程为:

(1)调用EventHub的getEvents方法获取输入事件

(2)调用processEventsLocked()方法处理获取到的输入事件

(3)调用flush()方法将已经封装好的EventEntry事件交给给InputDispatcher分发

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

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

相关文章

LeetCode 热题 HOT 100 (025/100)【宇宙最简单版】

【二叉树】No. 0124 二叉树中的最大路径和 【困难】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你…

12.C基础_字符串相关函数

字符串长度strlen 头文件 #include <string.h> 函数声明 size_t strlen(const char *s); 返回值&#xff1a;long型的字符串长度 s&#xff1a;要计算长度的字符串&#xff0c;必须以 \0 结尾 注意&#xff1a;strlen计算的长度是以 \0 为结尾&#xff0c;而sizeo…

【书生大模型实战营(暑假场)闯关材料】入门岛:第1关 Linux 基础知识

【书生大模型实战营&#xff08;暑假场&#xff09;闯关材料】入门岛&#xff1a;第1关 Linux 基础知识 1. 使用VScode进行SSH远程连接服务器2. 端口映射及实例参考文献 这一博客主要介绍使用VScode进行服务器远程连接及端口映射。 1. 使用VScode进行SSH远程连接服务器 安装V…

一个基于 vue 的强大表单和高性能表格组件,简洁API设计,支持虚拟树,列拖拽,懒加载,快捷菜单(附源码)

前言 在现代Web应用开发中&#xff0c;表单和表格是两个核心组件&#xff0c;它们对于数据展示和用户交互至关重要。然而&#xff0c;现有的解-决方案往往存在一些痛点&#xff0c;如不够灵活、性能问题、以及难以实现复杂功能等。这些问题限制了开发者的创造力&#xff0c;也…

树莓派开发相关知识二

1、相关术语 树莓派装载的芯片&#xff1a;BCM2835是一个MCU微处理器&#xff0c;它可以理解为CPU其它模块的组合。 GPIO&#xff1a;General-purpose input/output&#xff0c;通用型输入输出&#xff0c;其接脚可以供使用者由程控自由使用&#xff0c;PIN脚依现实考量可作为…

java或者sh脚本实现 实现 mysql 数据库指定表,定时导出并导入指定数据库并切换指定字段名(适合linux和windows)

定时导出指定数据库的指定表导出到指定数据库 一、Java实现 1、contronller Slf4j Controller public class BackupController {AutowiredBackupService backupService;// 备份 // ResponseBody // PostMapping("/backup/backupByfile")Scheduled(cron&quo…

音视频开发

通过多线程分别获取高分辨率(1920 * 1080)和低分辨率(1280 * 720) 初始化VI模块 初始化HIGH VENC模块 初始化LOW VENC模块 初始化RGA模块 绑定 VI和HIGH VENC 绑定 VI和RGA 创建线程 HIGH VENC处理 RGA处理 LOW VENC处理 销毁 QP原理的讲解 QP参数调节&#xff0c;指的是量化…

C:每日一题:二分查找

1、知识介绍&#xff1a; 1.1 概念&#xff1a; 二分查找是一种在有序数组中查找某一特定元素的搜索算法 1.2 基本思想&#xff1a; 每次将待查找的范围缩小一半&#xff0c;通过比较中间元素与目标元素的大小&#xff0c;来决定是在左半部分还是右半部分继续查找。 举个生…

进程状态和线程

一、wait&#xff08;阻塞调用&#xff09; pid_t wait(int *status); 1.功能&#xff1a;&#xff08;1&#xff09;获取子进程退出状态 &#xff08;2&#xff09;.回收资源 //会让僵尸态的子进程销毁 参数 int *status: 指向一个整数的指针&#xff0c;wait …

qtday01

实现一个登录窗口 #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {//设置标题this->setWindowTitle("小黑子登录器");//设置图标this->setWindowIcon(QIcon("E:\\qt\\day1_04\\pictrue\\cxk.gif"));//固定…

移动APP测试有哪些注意事项?专业APP测试报告如何获取?

移动APP在其生命周期中有不同的阶段&#xff0c;从开始到投入目标市场再到被淘汰。移动APP的成功有多种因素&#xff0c;例如创建、部署、推广、粘性等。但是&#xff0c;创建出色APP的关键在于它的测试&#xff0c;软件测试负责为客户提供安全有效的产品&#xff0c;因此移动A…

Haproxy实现https

haproxy可以实现https的证书安全,从用户到haproxy为https,从haproxy到后端服务器用http通信 &#xff0c;但是基于性能考虑,生产中证书都是在后端服务器比如nginx上实现。 配置HAProxy支持https协议&#xff0c;支持ssl会话&#xff1a; bind *:443 ssl crt /PATH/TO/SOME_PEM…

【C++ 面试 - 基础题】每日 3 题(十)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

如何把命令行创建python虚拟环境与pycharm项目管理更好地结合起来

1. 问题的提出 我在linux或windows下的某个目录如“X”下使用命令行的方式创建了一个python虚拟环境&#xff08;参考文章&#xff09;&#xff0c;对应的目录为myvenv, 现在我想使用pycharm创建python项目myproject&#xff0c;并且利用虚拟环境myvenv&#xff0c;怎么办&…

搭建jenkins+k8s过程中遇到的问题

1、k8s地址配置导致的一些个问题 Still waiting to schedule task ‘Jenkins’ doesn’t have label ‘k8s-jenkins-slave’ 出现以上异常一般就是k8s地址配置不对或者地址不通导致的 配置完k8s地址以及命名空间等内容之后,在获取k8s下的token 1.查看sa # kubectl get sa…

我的第一个CUDA程序

MatAdd算法 实现两个矩阵对应元素相加 MatAdd算法的GPU实现 CPU端为输入矩阵A和B、输出矩阵C分配空间&#xff0c;并进行初始化CPU端分配设备端内存&#xff0c;并将A和B传输到GPU上定义数据和线程的映射关系&#xff0c;并确定线程的开启数量和组织方式 每个线程负责输出矩阵…

EasyX自学笔记3(割草游戏1)

割草游戏&#xff0c;有玩家&#xff08;上下左右控制移动&#xff09;周围围绕子弹&#xff0c;敌人&#xff08;随机刷新&#xff09;向玩家靠近&#xff0c;子弹打死敌人&#xff0c;玩家与敌人触碰游戏结束。 分析需求 1.有玩家、敌人、子弹三种对象 2.玩家上下左右控制…

Spring MVC数据绑定和响应学习笔记

学习视频:12001 数据绑定_哔哩哔哩_bilibili 目录 1.数据绑定 简单数据绑定 默认类型数据绑定 简单数据类型绑定的概念 参数别名的设置 PathVariable注解的两个常用属性 POJO绑定 自定义类型转换器 xml方式 注解方式 数组绑定 集合绑定 复杂POJO绑定 属性为对象类…

力扣面试经典算法150题:最长公共前缀

最长公共前缀 今天的题目是力扣面试经典150题中的数组的简单题: 最长公共前缀 题目链接&#xff1a;https://leetcode.cn/problems/longest-common-prefix/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 编写一个函数来查找字符串数组中的最长公…

修改OpenSSH服务版本号

前言 这几年信息安全要求很高&#xff0c;奈何口号响亮掩盖不了我们技术基础依然很低的事实&#xff0c;加上风口烧钱和政绩工程等因素&#xff0c;于是就诞生了一些乱象&#xff0c;其中一个就是安全扫描胡乱标记&#xff0c;这里面的典型就是OpenSSH的漏洞扫描报告。 比如&…