Android 12系统源码_输入系统(三)输入事件的加工和分发

前言

上一篇文章我们具体分析了InputManagerService的构造方法和start方法,知道IMS的start方法经过层层调用,最终会触发Navite层InputDispatcher的start方法和InputReader的start方法。InputDispatcher的start方法会启动一个名为InputDispatcher的线程,该线程会循环调用自己的dispatchOnce方法;InputReader的start方法会启动一个名为InputReader的线程,该线程会循环调用自己的loopOnce方法;并绘制了一个简单的IMS架构图。
在这里插入图片描述
本篇文章我们将在此基础上,继续结合Navite层的InputDispatcher和InputReader的源码,来分析Android系统是如何对输入事件进行加工和分发的。

一、输入事件的加工

1.1 InputReader的start方法

>frameworks/>native/services/inputflinger/reader/InputReader.cpp
status_t InputReader::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    //开启InputThread线程,并循环调用loopOnce方法。
    mThread = std::make_unique<InputThread>(
            "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
    return OK;
}

void InputReader::loopOnce() {
	 ...代码省略...
	//从事件缓冲区中获取设备节点的事件信息并将其存放到mEventBuffer中
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
	 ...代码省略...
     if (count) {
         //如果有事件,则调用processEventsLocked方法处理这些事件。
         processEventsLocked(mEventBuffer, count);
     }
     ...代码省略...
}

InputReader的start方法会开启一个名为InputThread的线程,该线程会循环调用loopOnce方法,该方法先是从事件缓冲区中获取设备节点的事件信息并将其存放到mEventBuffer中,如果有事件,则调用processEventsLocked方法处理这些事件。

1.2 InputReader的processEventsLocked方法

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

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    //注释1,遍历所有事件
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        //注释2,事件类型分为原始输入事件和设备事件,这个条件语句对原始输入事件进行处理
        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;
            }
            //处理deviceId所对应设备的原始输入事件
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            //注释3,对设备事件进行处理
            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;
    }
   

在注释1处会对传递进来的所有事件进行遍历。在注释2处会判断事件是输入事件还是设备事件,如果是输入事件,会调用processEventsForDeviceLocked方法。在注释3处判断如果是事件是设备事件,则会进一步判断设备事件的具体类型,针对不同的事件类型进行不同的操作,如果是设备新增,则调用addDeviceLocked方法,如果是设备移除,则调用removeDeviceLocked,如果配置变化,则调用handleConfigurationChangedLocked方法。

1.2.1 InputReader的processEventsForDeviceLocked方法

>frameworks/>native/services/inputflinger/reader/InputReader.cpp
//处理同一个设备的输入事件
void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
                                               size_t count) {
    //注释1,通过设备id获取设备对象                                           
    auto deviceIt = mDevices.find(eventHubId);
    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;
    }
    //注释,2,调用InputDevices的process方法
    device->process(rawEvents, count);
}

>frameworks/native/services/inputflinger/reader/InputDevice.cpp
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
	//遍历输入事件
    for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
    	 ...代码省略...
    	 //C++ Lambda 表达式,遍历与输入事件对应的设备ID关联的所有映射器,并调用每个映射器的process方法处理事件。
         for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) {
         	 //调用InputMapper的process方法
             mapper.process(rawEvent);
         });
        ...代码省略...
        --count;
    }
}

>frameworks/native/services/inputflinger/reader/include/InputDevice.h
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) {//获取设备对象的InputMapper集合,循环执行其方法,其实就是process方法
            f(*mapperPtr);
        }
    }
}
}

在注释1处通过deviceId获取输入设备在mDevices中实例对象,然后调用InputDevice实例对象的process方法,process方法会遍历所有输入事件,获取每个输入事件对应的设备ID关联的所有InputMapper映射器,并调用这些映射器的process方法处理事件。那么这些映射器是在什么地方被初始化的呢?

1.2.2 InputReader的addDeviceLocked方法

重新回到前面InputReader的processEventsLocked方法中,当一个输入事件类型为设备事件类型,且该设备事件类型为设备新增类型的时候,会调用addDeviceLocked方法来处理。

>frameworks/>native/services/inputflinger/reader/InputReader.cpp
void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {
    if (mDevices.find(eventHubId) != mDevices.end()) {
        ALOGW("Ignoring spurious device added event for eventHubId %d.", eventHubId);
        return;
    }
    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
    //注释1,调用createDeviceLocked方法创建InputDevice对象
    std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
    ...代码省略...
    //注释2,将新创建的device对象添加到事件节点设备对象集合中
    mDevices.emplace(eventHubId, device);
	...代码省略...
}

std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
        int32_t eventHubId, const InputDeviceIdentifier& identifier) {
    auto deviceIt = std::find_if(mDevices.begin(), mDevices.end(), [identifier](auto& devicePair) {
        const InputDeviceIdentifier identifier2 =
                devicePair.second->getDeviceInfo().getIdentifier();
        return isSubDevice(identifier, identifier2);
    });

    std::shared_ptr<InputDevice> device;
    if (deviceIt != mDevices.end()) {
        device = deviceIt->second;
    } else {
        int32_t deviceId = (eventHubId < END_RESERVED_ID) ? eventHubId : nextInputDeviceIdLocked();
        //创建输入设备对象
        device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
                                               identifier);
    }
    //注释3,调用InputDevice的addEventHubDevice方法,将输入事件添加到输入设备对象中
    device->addEventHubDevice(eventHubId);
    return device;
}

在注释1处调用createDeviceLocked方法创建InputDevice对象。在注释2处将新创建的device对象添加到事件节点设备对象集合中。在注释3处,也就是createDeviceLocked方法中,调用InputDevice的addEventHubDevice方法,将输入事件添加到输入设备对象中。

1.3 InputDevice的addEventHubDevice方法

>frameworks/native/services/inputflinger/reader/include/InputDevice.h
class InputDevice {
private:
    using MapperVector = std::vector<std::unique_ptr<InputMapper>>;
    using DevicePair = std::pair<std::unique_ptr<InputDeviceContext>, MapperVector>;
    哈希映射表,里面存放了和eventHubId 关联的设备上下文和映射器列表
    std::unordered_map<int32_t, DevicePair> mDevices;
}
>frameworks/native/services/inputflinger/reader/InputDevice.cpp
void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) {
    //注释1,检查是否已存在指定的设备
    if (mDevices.find(eventHubId) != mDevices.end()) {
        return;
    }
    //创建新的设备上下文
    std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
    //获取设备类别
    Flags<InputDeviceClass> classes = contextPtr->getDeviceClasses();
    //创建输入事件映射器列表
    std::vector<std::unique_ptr<InputMapper>> mappers;

    if (!populateMappers) {
        mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
        return;
    }
    //注释2,根据设备类别创建相关的输入映射器
    //开关设备
    if (classes.test(InputDeviceClass::SWITCH)) {
        mappers.push_back(std::make_unique<SwitchInputMapper>(*contextPtr));
    }

    //旋转编码器设备
    if (classes.test(InputDeviceClass::ROTARY_ENCODER)) {
        mappers.push_back(std::make_unique<RotaryEncoderInputMapper>(*contextPtr));
    }

    //振动设备
    if (classes.test(InputDeviceClass::VIBRATOR)) {
        mappers.push_back(std::make_unique<VibratorInputMapper>(*contextPtr));
    }

    //电池或光源设备
    if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) {
        mController = std::make_unique<PeripheralController>(*contextPtr);
    }

    //键盘和游戏控制器
    uint32_t keyboardSource = 0;
    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
    if (classes.test(InputDeviceClass::KEYBOARD)) {
        keyboardSource |= AINPUT_SOURCE_KEYBOARD;
    }
    if (classes.test(InputDeviceClass::ALPHAKEY)) {
        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
    }
    if (classes.test(InputDeviceClass::DPAD)) {
        keyboardSource |= AINPUT_SOURCE_DPAD;
    }
    if (classes.test(InputDeviceClass::GAMEPAD)) {
        keyboardSource |= AINPUT_SOURCE_GAMEPAD;
    }

    if (keyboardSource != 0) {
        mappers.push_back(std::make_unique<KeyboardInputMapper>(*contextPtr, keyboardSource, keyboardType));
    }

    //光标设备
    if (classes.test(InputDeviceClass::CURSOR)) {
        mappers.push_back(std::make_unique<CursorInputMapper>(*contextPtr));
    }

    //触摸屏和触摸板设备
    if (classes.test(InputDeviceClass::TOUCH_MT)) {
        mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
    } else if (classes.test(InputDeviceClass::TOUCH)) {
        mappers.push_back(std::make_unique<SingleTouchInputMapper>(*contextPtr));
    }

    //操纵杆设备
    if (classes.test(InputDeviceClass::JOYSTICK)) {
        mappers.push_back(std::make_unique<JoystickInputMapper>(*contextPtr));
    }

    //运动传感器设备
    if (classes.test(InputDeviceClass::SENSOR)) {
        mappers.push_back(std::make_unique<SensorInputMapper>(*contextPtr));
    }

    //外部手写笔设备
    if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) {
        mappers.push_back(std::make_unique<ExternalStylusInputMapper>(*contextPtr));
    }

    //注释3,将设备上下文和事件映射器插入到哈希映射表中
    mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
    //更新设备生成代号
    bumpGeneration();
}

在注释1处检查是否已存在指定的设备,如果存在直接返回,如果设备不存在,则创建新的设备上下文和相关的事件映射器列表。
在注释2处会根据设备类别创建相关的输入映射器,比如开关设备(SwitchInputMapper)、旋转编码器设备(RotaryEncoderInputMapper)、振动设备(VibratorInputMapper)、电池或光源设备(PeripheralController)、键盘设备(KeyboardInputMapper)、光标设备(CursorInputMapper)、触摸屏(MultiTouchInputMapper)和触摸板设备(SingleTouchInputMapper)、操纵杆设备(JoystickInputMapper)、运动传感器设备(SensorInputMapper)、外部手写笔设备(ExternalStylusInputMapper)。在注释3处将前面创建的设备上下文和事件映射器添加到哈希映射表mDevices中,方便后续查找,最后调用bumpGeneration方法更新设备生成代号。

1.4 InputMapper处理输入事件

重新回到前面InputReader的processEventsLocked方法中,当一个事件类型为原始输入事件类型的时候,会调用InputReader的processEventsForDeviceLocked方法,然后调用InputDevice的process方法,该方法会遍历自己所有的事件映射器,将原始输入事件交给InputMapper的process方法来出来,至于到底是那个InputMapper来处理,InputReader并不关心。

1.4.1 KeyboardInputMapper的process方法

这里我们结合处理键盘输入事件的KeyboardInputMapper这个映射器的process方法来作进一步分析。

>frameworks/native/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
//处理了键盘输入事件
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
        case EV_KEY: {
            int32_t scanCode = rawEvent->code;
            int32_t usageCode = mCurrentHidUsage;
            mCurrentHidUsage = 0;
            if (isKeyboardOrGamepadKey(scanCode)) {
                //注释1,处理按键事件
                processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, scanCode,
                           usageCode);
            }
            break;
        }
        ...代码省略...
    }
}

void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode,
                                     int32_t usageCode) {
	...代码省略...
    //注释2,将原始事件封装成一个新的NotifyKeyArgs对象
    NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                       getDisplayId(), policyFlags,
                       down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
                       AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
    //注释3,调用监听者的notifyKey方法,这里其实就是InputDispatcher对象
    getListener()->notifyKey(&args);
}

在注释1处调用processKey方法处理按键事件。在注释2处,也就是processKey方法内,将原始事件封装为一个新的NotifyKeyArgs对象。在注释3处,先是调用getListener方法获取监听者,然后调用监听者的notifyKey方法。这里获取的监听者最早是在InputReader的构造方法中初始化的。

1.4.2 InputReader初始化监听者

InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
                         const sp<InputReaderPolicyInterface>& policy,
                         const sp<InputListenerInterface>& listener)
      : mContext(this),
        mEventHub(eventHub),
        mPolicy(policy),
        mGlobalMetaState(0),
        mLedMetaState(AMETA_NUM_LOCK_ON),
        mGeneration(1),
        mNextInputDeviceId(END_RESERVED_ID),
        mDisableVirtualKeysTimeout(LLONG_MIN),
        mNextTimeout(LLONG_MAX),
        mConfigurationChangesToRefresh(0) {
    mQueuedListener = new QueuedInputListener(listener);//初始化监听者
    { // acquire lock
        std::scoped_lock _l(mLock);
        refreshConfigurationLocked(0);
        updateGlobalMetaStateLocked();
    } // release lock
}

InputListenerInterface* InputReader::ContextImpl::getListener() {
    return mReader->mQueuedListener.get();
}

>frameworks/native/services/inputflinger/include/InputListener.h
class QueuedInputListener : public InputListenerInterface {
private:
    sp<InputListenerInterface> mInnerListener;//监听者
    std::vector<NotifyArgs*> mArgsQueue;
};
>frameworks/native/services/inputflinger/InputListener.cpp
QueuedInputListener::QueuedInputListener(const sp<InputListenerInterface>& innerListener) :
        mInnerListener(innerListener) {
}

而InputReader最早是在InputManager的构造方法中被创建的。

>frameworks/native/services/inputflinger/InputManager.cpp
InputManager::InputManager(
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    //创建inputDispatcher对象
    mDispatcher = createInputDispatcher(dispatcherPolicy);
    mClassifier = new InputClassifier(mDispatcher);
    //注释1,创建InputReader对象,其内部持有mDispatcher的引用
    mReader = createInputReader(readerPolicy, mClassifier);
}

>frameworks/native/services/inputflinger/reader/InputReaderFactory.cpp
sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
                                           const sp<InputListenerInterface>& listener) {
    //创建InputReader对象
    return new InputReader(std::make_unique<EventHub>(), policy, listener);
}

在注释1处调用createInputReader方法,这里传入的第二个参数就是InputDispatcher,其内部会调用InputReader的构造方法。

1.4.3 KeyboardInputMapper的loopOnce方法

重新回到KeyboardInputMapper的processKey方法中。

>frameworks/native/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
//处理了键盘输入事件
void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode,
                                     int32_t usageCode) {
	...代码省略...
    NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                       getDisplayId(), policyFlags,
                       down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
                       AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
    //调用监听者的notifyKey方法
    getListener()->notifyKey(&args);
}

该方法的最后会调用监听者的notifyKey方法。

>frameworks/native/services/inputflinger/InputListener.cpp
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
    traceEvent(__func__, args->id);
    //将NotifyKeyArgs对象存储到mArgsQueue队列中
    mArgsQueue.push_back(new NotifyKeyArgs(*args));
}

void QueuedInputListener::flush() {
   	...对事件进行处理...
}

可以发现QueuedInputListener的notifyKey方法只是将NotifyKeyArgs事件对象存储到mArgsQueue队列中,并没有真正对事件进行处理,真正对事件进行处理是在flush方法中,而QueuedInputListener的flush方法是在InputReader的loopOnce方法中被调用的。关于loopOnce这个方法的具体分析请参考Android 12系统源码_输入系统(二)InputManagerService服务这篇文章.

>frameworks/>native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() {
  	...代码省略...
    mQueuedListener->flush();//刷新或处理排队的监听器事件。
}
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();
}

QueuedInputListener的flush方法就是循环调用列表中的事件,并依次执行NotifyArgs的notify方法,传入的参数mInnerListener为初始化时的InputDispatcher对象。

1.4.4 NotifyArgs的notify方法

>frameworks/native/services/inputflinger/include/InputListener.h
struct NotifyArgs {
    virtual void notify(const sp<InputListenerInterface>& listener) const = 0;
};

notify方法是一个纯虚函数,由其子类实现。KeyboardInputMapper事件映射器对应的子类就是NotifyKeyArgs对象。

>struct NotifyKeyArgs : public NotifyArgs {
    virtual void notify(const sp<InputListenerInterface>& listener) const;
}

>frameworks/native/services/inputflinger/InputListener.cpp
NotifyKeyArgs::NotifyKeyArgs(const NotifyKeyArgs& other)
      : NotifyArgs(other.id, other.eventTime),
        deviceId(other.deviceId),
        source(other.source),
        displayId(other.displayId),
        policyFlags(other.policyFlags),
        action(other.action),
        flags(other.flags),
        keyCode(other.keyCode),
        scanCode(other.scanCode),
        metaState(other.metaState),
        downTime(other.downTime),
        readTime(other.readTime) {}
        
void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyKey(this);//注释1
}

在注释1处调用listener的notifyKey方法,这里的listener就是InputDispatcher的对象。

1.5 InputDispatcher的notifyKey方法

>frameworks/native/services/inputflinger/dispatcher/InputDispatcher.h
class InputDispatcher : public android::InputDispatcherInterface, public gui::WindowInfosListener {
private:
	//这是一个双端队列,用于存储输入事件条目(EventEntry)
	//std::shared_ptr 表示使用智能指针来管理事件条目的生命周期。
	//GUARDED_BY(mLock)表明 mInboundQueue 由 mLock 进行保护,确保在多线程环境中对队列的访问是安全的。
    std::deque<std::shared_ptr<EventEntry>> mInboundQueue GUARDED_BY(mLock);
}
>frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
	...代码省略...
    bool needWake;
    { // acquire lock
        mLock.lock();
		...代码省略...
        //注释1
        std::unique_ptr<KeyEntry> newEntry =
                std::make_unique<KeyEntry>(args->id, args->eventTime, args->deviceId, args->source,
                                           args->displayId, policyFlags, args->action, flags,
                                           keyCode, args->scanCode, metaState, repeatCount,
                                           args->downTime);
        //注释2
        needWake = enqueueInboundEventLocked(std::move(newEntry));
        mLock.unlock();
    } // release lock

    if (needWake) {
        //注释3
        mLooper->wake();
    }
}

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
    bool needWake = mInboundQueue.empty();
    //将newEntry对象压入mInboundQueue中
    mInboundQueue.push_back(std::move(newEntry));
   ...代码省略...
}

由于进入了InputDispatcher的“势力范围”,在注释1处会将NotifyKeyArgs事件重新封装为KeyEntry对象。在注释2处调用enqueueInboundEventLocked方法,该方法内部会将KeyEntry对象压入mInboundQueue中。在注释3处唤醒InputDispatche对应的线程。

1.6 输入事件的加工流程小结

1.SystemServer创建并启动InputManagerService
2.InputManagerService在native层创建一个NativeInputManager对象
3.NativeInputManager内部创建一个InputManager对象
4.InputManager的start方法会启动InputReader线程和InputDispatcher线程
5.在InputReader线程中调用EventHub的getEvents获取设备节点中的输入事件
6.并将输入事件封装为NotifyKeyArgs对象放入队列中
7.之后再调用flush,依次将事件传递给InputDispatcher
8.InputDispatcher在收到事件后,会重新封装为一个KeyEntry对象,并压力压入mInboundQueue列表中
9.最后唤醒InputDispatcherThread线程

二、输入事件的分发

我们在Android 12系统源码_输入系统(二)InputManagerService服务中有具体分析过InputDispatcher的start方法。

>frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
status_t InputDispatcher::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    //注释1,创建InputThread对象
    mThread = std::make_unique<InputThread>(
            "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
    return OK;
}

start方法会创建InputThread对象,该对象内部会启动名为InputDispatcher的线程,该线程会循环调用dispatchOnce方法。

2.1 InputDispatcher的dispatchOnce方法

>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();

        //注释1,检测缓存队列中是否没有等待处理的指令
        if (!haveCommandsLocked()) {
            //调用dispatchOnceInnerLocked方法
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
        //注释2,执行指令
        if (runCommandsLockedInterruptible()) {
              nextWakeupTime = LONG_LONG_MIN;
        }
        //检查ANR
        const nsecs_t nextAnrCheck = processAnrsLocked();
        nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
        //处理空闲状态
        if (nextWakeupTime == LONG_LONG_MAX) {
            mDispatcherEnteredIdle.notify_all();
        }
    } // release lock

    //计算InputDisPatcherThread需要休眠的最长时间
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    //让mLooper线程休眠,timeoutMillis是休眠的最长时间
    mLooper->pollOnce(timeoutMillis);
}

在注释1处判断如果缓存队列中没有等待处理的指令,则会调用dispatchOnceInnerLocked方法来处理输入事件。如果当前有等待处理的指令,则在注释2处优先处理该指令,并将线程休眠时间nextWakeupTime设置为LONG_LONG_MIN,这样线程在执行完指令之后可以立刻被唤醒去处理输入事件。从这里可以看出dispatchOnce主要是做了两个功能:1.执行指令 2.处理输入事件,且指令执行优先级高于输入事件处理。例如对waitQueue中的事件进行出栈就属于指令的一种,这个后面我们还会讲到。

2.2 InputDispatcher的dispatchOnceInnerLocked方法

>frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now();
    if (!mDispatchEnabled) {
        //重置按键重复计数器
        resetKeyRepeatLocked();
    }
    //注释1,如果InputDispatcher被冻结,则不进行派发操作直接返回
    if (mDispatchFrozen) {
        if (DEBUG_FOCUS) {
            ALOGD("Dispatch frozen.  Waiting some more.");
        }
        return;
    }
    //注释2,如果isAppSwitchDue为true,则说明没有及时响应Home键操作
    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
    if (mAppSwitchDueTime < *nextWakeupTime) {
        *nextWakeupTime = mAppSwitchDueTime;
    }

    if (!mPendingEvent) {
        //如果mInboundQueue为空,并且没有待分发的事件,直接返回
        if (mInboundQueue.empty()) {
			...代码省略...
            if (!mPendingEvent) {
                return;
            }
        } else {
            //注释3,将输入事件队列mInboundQueue最前端条目取出赋值给mPendingEvent
            mPendingEvent = mInboundQueue.front();
            mInboundQueue.pop_front();
            traceInboundQueueLengthLocked();
        }

        // Poke user activity for this event.
        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            pokeUserActivityLocked(*mPendingEvent);
        }
    }
    
    ALOG_ASSERT(mPendingEvent != nullptr);
    bool done = false;
    //事件丢失的原因,默认为不丢失
    DropReason dropReason = DropReason::NOT_DROPPED;
    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
        dropReason = DropReason::POLICY;
    } else if (!mDispatchEnabled) {
        dropReason = DropReason::DISABLED;
    }

    if (mNextUnblockedEvent == mPendingEvent) {
        mNextUnblockedEvent = nullptr;
    }
    //注释4,判断输入事件的类型
    switch (mPendingEvent->type) {
		...代码省略...    
        case EventEntry::Type::KEY: {//按键事件
            std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
            if (isAppSwitchDue) {
                if (isAppSwitchKeyEvent(*keyEntry)) {
                    resetPendingAppSwitchLocked(true);
                    isAppSwitchDue = false;
                } else if (dropReason == DropReason::NOT_DROPPED) {
                    dropReason = DropReason::APP_SWITCH;
                }
            }
            //事件过期
            if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *keyEntry)) {
                dropReason = DropReason::STALE;
            }
            //阻碍其他窗口获取事件
            if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
                dropReason = DropReason::BLOCKED;
            }
            //注释5,调用dispatchKeyLocked方法分发按键事件
            done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
            break;
        }
        case EventEntry::Type::MOTION: {//触摸事件
            std::shared_ptr<MotionEntry> motionEntry =
                    std::static_pointer_cast<MotionEntry>(mPendingEvent);
            //如果没有及时响应窗口切换操作
            if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
                dropReason = DropReason::APP_SWITCH;
            }
            //事件过期
            if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
                dropReason = DropReason::STALE;
            }
            //阻碍其他窗口获取事件
            if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
                dropReason = DropReason::BLOCKED;
            }
            //调用dispatchMotionLocked方法分发触摸事件
            done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
            break;
        }
        case EventEntry::Type::SENSOR: {
            std::shared_ptr<SensorEntry> sensorEntry =
                    std::static_pointer_cast<SensorEntry>(mPendingEvent);
            if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
                dropReason = DropReason::APP_SWITCH;
            }
            nsecs_t bootTime = systemTime(SYSTEM_TIME_BOOTTIME);
            if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(bootTime, *sensorEntry)) {
                dropReason = DropReason::STALE;
            }
            dispatchSensorLocked(currentTime, sensorEntry, &dropReason, nextWakeupTime);
            done = true;
            break;
        }
    }

    if (done) {
        if (dropReason != DropReason::NOT_DROPPED) {
            dropInboundEventLocked(*mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
        //注释6,释放本次处理完毕的分发事件对象
        releasePendingEventLocked();
        //注释7,使InputDispatcher能够立刻进行下一次事件的分发
        *nextWakeupTime = LONG_LONG_MIN; 
    }
}

在注释1处,判断如果当前InputDispatcher被冻结,则不进行分发操作,InputDispatcher有三种状态:正常、冻结、禁用;可以通过InputDispatcher的setInputDispatchMode方法进行设置。在注释2处,mAppSwitchDueTime代表了App最近发生窗口切换操作时(比如按Home键,挂断电话),该操作事件最迟的分发时间,如果mAppSwitchDueTime小于或等于当前系统时间,说明没有及时响应窗口切换的操作,则isAppSwitchDue的值为true,如果mAppSwitchDueTime小于nextWakeupTime,这样当InputDispatcher处理完分发事件后,会立刻被唤醒执行窗口切换操作。在注释3处会将输入事件队列mInboundQueue最前端条目取出赋值给mPendingEvent。然后在注释4处判断输入事件的类型。如果为按键事件,则在注释5处调用dispatchKeyLocked方法开始分发按键事件。在注释6处会释放本次处理完毕的分发事件对象。然后将休眠时间设置为最短休眠时间,以便InputDispatcher能立刻被唤醒以便进行下一次事件的分发。

2.3 InputDispatcher的dispatchKeyLocked方法

>frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
	...代码省略...
    //如果事件是需要丢弃的,则返回true,不再为该事件寻找合适的窗口
    if (*dropReason != DropReason::NOT_DROPPED) {
        setInjectionResult(*entry,
                           *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
                                                             : InputEventInjectionResult::FAILED);
        mReporter->reportDroppedKey(entry->id);
        return true;
    }

    //目标窗口列表
    std::vector<InputTarget> inputTargets;
    //注释1,调用findFocusedWindowTargetsLocked查找焦点窗口
    InputEventInjectionResult injectionResult =
            findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
    //输入事件被挂起,说明找到了窗口但是窗口无响应
    if (injectionResult == InputEventInjectionResult::PENDING) {
        return false;
    }

    setInjectionResult(*entry, injectionResult);
    //输入事件没有分发成功,说明没有找到合适的窗口
    if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
        return true;
    }

    //将分发的目标添加到inputTargets列表中
    addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));

    //注释2,调用dispatchEventLocked方法
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

在注释1处会调用findFocusedWindowTargetsLocked查找当前的焦点窗口并将其存储到inputTargets中。然后在注释2处,会调用dispatchEventLocked方法将输入事件分发给inputTargets列表中的目标窗口。

2.4 InputDispatcher的dispatchEventLocked方法

>frameworks/native/services/inputflinger/dispatcher/InputDispatcher.h
class InputDispatcher : public android::InputDispatcherInterface, public gui::WindowInfosListener {
private:
    std::unordered_map<sp<IBinder>, sp<Connection>, StrongPointerHash<IBinder>> mConnectionsByToken
            GUARDED_BY(mLock);
}
>frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
                                          std::shared_ptr<EventEntry> eventEntry,
                                          const std::vector<InputTarget>& inputTargets) {
	...代码省略...
    pokeUserActivityLocked(*eventEntry);
    //遍历inputTargets列表
    for (const InputTarget& inputTarget : inputTargets) {
        //注释1,调用getConnectionLocked方法获取当前inputTarget对应的connection
        sp<Connection> connection = getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
        if (connection != nullptr) {
            //注释2,调用prepareDispatchCycleLocked方法开始事件分发循环
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
        } else {
            if (DEBUG_FOCUS) {
                ALOGD("Dropping event delivery to target with channel '%s' because it "
                      "is no longer registered with the input dispatcher.",
                      inputTarget.inputChannel->getName().c_str());
            }
        }
    }
}

sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) const {
    if (inputConnectionToken == nullptr) {
        return nullptr;
    }
    //遍历mConnectionsByToken
    for (const auto& [token, connection] : mConnectionsByToken) {
    	//如果token相等,则返回该对象
        if (token == inputConnectionToken) {
            return connection;
        }
    }
    return nullptr;
}

在注释1处调用getConnectionLocked方法,传入当前inputTarget对应的连接令牌,该方法内部会遍历当前存在的所有窗口连接,返回和当前inputTarget令牌相等的窗口连接对象connection 。然后在注释2处调用prepareDispatchCycleLocked方法开始事件分发循环。

2.5 输入事件的加工和分发流程小结

1.InputReader将设备节点中的输入事件进行加工并发送给InputDispatcher
2.InputDispatcher在收到事件后,会重新封装为一个KeyEntry对象,并压入mInboundQueue列表中,唤醒InputDispatcher线程
3.InputDispatcher线程会循环调用dispatchOnce方法,将存放在mInboundQueue列表中的输入事件依次取出,并判断根据输入事件的具体类型调用对应的事件分发方法
4.如果是按键事件,则会调用dispatchKeyLocked方法;如果是触摸事件,则会调用dispatchMotionLocked方法
5.在调用特定dispatch*Locked方法进行特定事件分发的过程中,都会先调用findFocusedWindowTargetsLocked方法查找当前系统的焦点窗口,然后将输入事件分发给焦点目标窗口

三、系统窗口状态数据的传递

我们在Android 12系统源码_窗口管理(二)WindowManager对窗口的管理过程这篇文章有讲过,代表窗口视图的Window是在ViewRootImpl的setView方法中传入WMS的。

frameworks/base/core/java/android/view/ViewRootImpl.java

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    
    final IWindowSession mWindowSession;
    final W mWindow;
    final View.AttachInfo mAttachInfo;

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
                 ...代码省略...
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
                ...代码省略...
                res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), userId,
                        mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                        mTempControls);
              ...代码省略...
    }
}
>frameworks/base/services/core/java/com/android/server/wm/Session.java
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
    @Override
    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId,
                UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
                outActiveControls);
    }
}
>frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
        
    //窗口管理策略的接口类,用来定义一个窗口策略所要遵循的通用规范,具体实现类为PhoneWindowManager。
    final WindowManagerPolicy mPolicy;
    
    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
		...代码省略...
        //创建窗口状态对象,该对象包含窗口所对应的所有信息,它就是要添加的窗口对象
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                appOp[0], attrs, viewVisibility, session.mUid, userId,
                session.mCanAddInternalSystemWindow);
		...代码省略...                
        //检验窗口是否可以被添加到系统中
        res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
        android.util.Log.d(TAG, "addWindow: res = "+res);
        if (res != ADD_OKAY) {
            return res;
        }
        final boolean openInputChannels = (outInputChannel != null
        && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if (openInputChannels) {
            win.openInputChannel(outInputChannel);
        }
		...代码省略...
    }
}
>frameworks/base/services/core/java/com/android/server/wm/WindowState.java
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
        InsetsControlTarget, InputTarget {
        
    void openInputChannel(InputChannel outInputChannel) {
        if (mInputChannel != null) {
            throw new IllegalStateException("Window already has an input channel.");
        }
        String name = getName();
        mInputChannel = mWmService.mInputManager.createInputChannel(name);
        mInputChannelToken = mInputChannel.getToken();
        mInputWindowHandle.setToken(mInputChannelToken);
        mWmService.mInputToWindowMap.put(mInputChannelToken, this);
        if (outInputChannel != null) {
            mInputChannel.copyTo(outInputChannel);
        } else {
            // If the window died visible, we setup a fake input channel, so that taps
            // can still detected by input monitor channel, and we can relaunch the app.
            // Create fake event receiver that simply reports all events as handled.
            mDeadWindowEventReceiver = new DeadWindowEventReceiver(mInputChannel);
        }
    }
    
}
>frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public class InputManagerService extends IInputManager.Stub
        implements Watchdog.Monitor {
    
    private static native InputChannel nativeCreateInputChannel(long ptr, String name);
    
    public InputChannel createInputChannel(String name) {
        return nativeCreateInputChannel(mPtr, name);
    }
}
>frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static jobject nativeCreateInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr,
                                        jstring nameObj) {
    //获取NativeInputManager 实例
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    //转换 Java 字符串
    ScopedUtfChars nameChars(env, nameObj);
    std::string name = nameChars.c_str();
    //注释1,创建Native层的InputChannel对象
    base::Result<std::unique_ptr<InputChannel>> inputChannel = im->createInputChannel(env, name);
    //如果创建 InputChannel 失败,生成错误消息并抛出一个 RuntimeException,返回 nullptr。
    if (!inputChannel.ok()) {
        std::string message = inputChannel.error().message();
        message += StringPrintf(" Status=%d", inputChannel.error().code());
        jniThrowRuntimeException(env, message.c_str());
        return nullptr;
    }
    //注释3,将Navite层的inputChannel对象转化为Java层的inputChannel对象
    jobject inputChannelObj = android_view_InputChannel_createJavaObject(env, std::move(*inputChannel));
    if (!inputChannelObj) {
        return nullptr;
    }
    //设置一个回调函数 handleInputChannelDisposed,以便在Java层InputChannel被垃圾回收时处理清理操作。
    android_view_InputChannel_setDisposeCallback(env, inputChannelObj, handleInputChannelDisposed, im);
    //返回Java层的InputChannel对象
    return inputChannelObj;
}

base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
        JNIEnv* /* env */, const std::string& name) {
    ATRACE_CALL();
    //注释2,调用InputDispatcher的createInputChannel方法
    return mInputManager->getDispatcher()->createInputChannel(name);
}

>frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const std::string& name) {
#if DEBUG_CHANNEL_CREATION
    ALOGD("channel '%s' ~ createInputChannel", name.c_str());
#endif

    std::unique_ptr<InputChannel> serverChannel;
    std::unique_ptr<InputChannel> clientChannel;
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);

    if (result) {
        return base::Error(result) << "Failed to open input channel pair with name " << name;
    }

    { // acquire lock
        std::scoped_lock _l(mLock);
        const sp<IBinder>& token = serverChannel->getConnectionToken();
        int fd = serverChannel->getFd();
        sp<Connection> connection =
                new Connection(std::move(serverChannel), false /*monitor*/, mIdGenerator);

        if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
            ALOGE("Created a new connection, but the token %p is already known", token.get());
        }
        mConnectionsByToken.emplace(token, connection);

        std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                            this, std::placeholders::_1, token);

        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);
    } // release lock

    // Wake the looper because some connections have changed.
    mLooper->wake();
    return clientChannel;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于深度学习的点云处理模型PointNet++学习记录

前面我们已经学习了Open3D&#xff0c;并掌握了其相关应用&#xff0c;但我们也发现对于一些点云分割任务&#xff0c;我们采用聚类等方法的效果似乎并不理想&#xff0c;这时&#xff0c;我们可以想到在深度学习领域是否有相关的算法呢&#xff0c;今天&#xff0c;我们便来学…

给Windows系统设置代理的操作方法

一、什么是代理 网络代理是一种特殊的网络服务&#xff0c;允许一个网络终端通过这个服务与另一个网络终端进行非直接的连接&#xff0c;而提供代理服务的电脑系统或其它类型的网络终端被称为代理服务器。 代理服务器是网络信息的中转站&#xff0c;代理服务器就像是一个很大的…

DBC差异比较工具DBCCompare_原理介绍(四)

DBC比对工具UI图片 DBC比对工具&#xff1a;功能详解与源码分析 在现代汽车开发和诊断过程中&#xff0c;DBC&#xff08;Database Container&#xff09;文件扮演着至关重要的角色。它们详细描述了CAN&#xff08;Controller Area Network&#xff09;网络中各消息和信号的详…

GB28181信令交互流程及Android端设备对接探讨

GB28181规范必要性 好多开发者在做比如执法记录仪、智能安全帽、智能监控等设备端视频回传技术方案选型的时候&#xff0c;不清楚到底是用RTSP、RTMP还是GB28181&#xff0c;对GB28181相对比较陌生&#xff0c;我们就GB28181规范的必要性&#xff0c;做个探讨&#xff1a; 实现…

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第十八章 Linux编写第一个自己的命令

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

企业安全策略制定

如今&#xff0c;网络安全是所有组织的必需品&#xff0c;而不是奢侈品。现代企业面临着针对其数据、网络和系统的复杂且不断演变的威胁。 即使一个漏洞也可能导致严重违规、财务损失和声誉受损。正如堡垒依靠多层防御共同作用一样&#xff0c;公司的安全措施必须作为一个整体…

【学习笔记】手写 Tomcat 六

目录 一、线程池 1. 构建线程池的类 2. 创建任务 3. 执行任务 测试 二、URL编码 解决方案 测试 三、如何接收客户端发送的全部信息 解决方案 测试 四、作业 1. 了解工厂模式 2. 了解反射技术 一、线程池 昨天使用了数据库连接池&#xff0c;我们了解了连接池的优…

渗透测试--文件上传常用绕过方式

文件上传常用绕过方式 1.前端代码&#xff0c;限制只允许上传图片。修改png为php即可绕过前端校验。 2.后端校验Content-Type 校验文件格式 前端修改&#xff0c;抓取上传数据包&#xff0c;并且修改 Content-Type 3.服务端检测&#xff08;目录路径检测&#xff09; 对目…

医院体检管理系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;体检分类管理&#xff0c;体检套餐管理&#xff0c;体检预约管理&#xff0c;体检报告管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;体检套餐&a…

四、Drf认证组件

四、Drf认证组件 4.1 快速使用 from django.shortcuts import render,HttpResponse from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.authentication import BaseAuthentication from rest_framework.exception…

数据结构:将复杂的现实问题简化为计算机可以理解和处理的形式

整句话的总体意义是&#xff0c;**数据结构是用于将现实世界中的实体和关系抽象为数学模型&#xff0c;并在计算机中表示和实现的关键工具**。它不仅包括如何存储数据&#xff0c;还包括对这些数据的操作&#xff0c;能够有效支持计算机程序的运行。通过这一过程&#xff0c;数…

利用PDLP扩展线性规划求解能力

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Java项目实战II基于Java+Spring Boot+MySQL的甘肃非物质文化网站设计与实现(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者 一、前言 甘肃省作为中国历史文化名省&#xff0c;拥有丰富的非物质文化遗产资源&#xff0c;涵盖表演艺术、手…

TypeScript 封装 Axios 1.7.7

随着Axios版本的不同&#xff0c;类型也在改变&#xff0c;以后怎么写类型&#xff1f; 1. 封装Axios 将Axios封装成一个类&#xff0c;同时重新封装request方法 重新封装request有几个好处&#xff1a; 所有的请求将从我们定义的requet请求中发送&#xff0c;这样以后更换…

Golang | Leetcode Golang题解之第441题排列硬币

题目&#xff1a; 题解&#xff1a; func arrangeCoins(n int) int {return sort.Search(n, func(k int) bool { k; return k*(k1) > 2*n }) }

【Unity服务】如何使用Unity Version Control

Unity上的线上服务有很多&#xff0c;我们接触到的第一个一般就是Version Control&#xff0c;用于对项目资源的版本管理。 本文介绍如何为项目添加Version Control&#xff0c;并如何使用&#xff0c;以及如何将项目与Version Control断开链接。 其实如果仅仅是对项目资源进…

09_OpenCV彩色图片直方图

import cv2 import numpy as np import matplotlib.pyplot as plt %matplotlib inlineimg cv2.imread(computer.jpeg, 1) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.imshow(img) plt.show()plot绘制直方图 plt.hist(img.ravel(), 256) #ravel() 二维降一维 256灰度级…

学习记录:js算法(五十):二叉树的右视图

文章目录 二叉树的右视图我的思路网上思路 总结 二叉树的右视图 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 图一&#xff1a; 示例 1:如图一 输入: [1,2,3,null,5,null,4] …

C++面向对象基础

目录 一.函数 1.内联函数 2.函数重载 3.哑元函数 二.类和对象 2.1 类的定义 2.2 创建对象 三. 封装&#xff08;重点&#xff09; 四. 构造函数 constructor&#xff08;重点&#xff09; 4.1 基础使用 4.2 构造初始化列表 4.3 构造函数的调用方式&#xff08;掌握…

解决方法:PDF文件打开之后不能打印?

打开PDF文件之后&#xff0c;发现文件不能打印&#xff1f;这是什么原因&#xff1f;首先我们需要先查看一下自己的打印机是否能够正常运行&#xff0c;如果打印机是正常的&#xff0c;我们再查看一下&#xff0c;文件中的打印功能按钮是否是灰色的状态。 如果PDF中的大多数功…