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.cppvoid 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 happenbreak;}}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 lockstd::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 lockmLock.lock();...代码省略...//注释1std::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);//注释2needWake = enqueueInboundEventLocked(std::move(newEntry));mLock.unlock();} // release lockif (needWake) {//注释3mLooper->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 lockstd::scoped_lock _l(mLock);mDispatcherIsAlive.notify_all();//注释1,检测缓存队列中是否没有等待处理的指令if (!haveCommandsLocked()) {//调用dispatchOnceInnerLocked方法dispatchOnceInnerLocked(&nextWakeupTime);}//注释2,执行指令if (runCommandsLockedInterruptible()) {nextWakeupTime = LONG_LONG_MIN;}//检查ANRconst 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最前端条目取出赋值给mPendingEventmPendingEvent = 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>> mConnectionsByTokenGUARDED_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对应的connectionsp<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;}//遍历mConnectionsByTokenfor (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 {@Overridepublic 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.Stubimplements 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.Stubimplements 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_CREATIONALOGD("channel '%s' ~ createInputChannel", name.c_str());
#endifstd::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 lockstd::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;
}

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

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

相关文章

linux驱动编程——等待队列

一、等待队列 可实现调用read函数时阻塞等。 1、流程 &#xff08;1&#xff09;初始化等待队列头&#xff08;带参宏&#xff09; init_waitqueue_head(q) 等待队列头wq数据类型&#xff1a; wait_queue_head_t&#xff0c;等待条件condition&#xff1a;int型变量。 &…

(c++)内存四区:1.代码区2.全局区(静态区)3.栈区4.堆区

//内存四区&#xff1a;1.代码区 2.全局区 3.栈区 4.堆区 1.放在代码区的有&#xff1a;1.写的代码&#xff1a;只读的、共享的、存放的二进制机器指令、由操作系统直接管理 2.放在全局区的有&#xff1a;1.全局的&#xff08;变量或常量&#xff09; 2.静态的&#xff0…

rdp远程桌面服务协议概述

rdp远程桌面服务协议概述 什么是远程桌面服务远程桌面服务的通信过程及功能 建立连接资源重定向与用户体验断开连接 远程桌面服务的协议架构 核心协议与基础通信虚拟通道与扩展协议协议协作与层次划分协议的可扩展性协议扩展与性能优化 总结参考 rdp远程桌面服务协议概述 对于…

SpringBoot(Java)实现MQTT连接(本地Mosquitto)通讯调试

1.工作及使用背景 工作中需要跟收集各种硬件或传感器数据用于Web展示及统计计算分析&#xff0c;如电表、流量计、泵、控制器等物联网设备。 目前的思路及解决策略是使用力控或者杰控等组态软件实现数据的转储&#xff08;也会涉及收费问题&#xff09;&#xff0c;通过组态软件…

鸿蒙开发(NEXT/API 12)【应用间消息通信】手机侧应用开发

在手机侧与穿戴设备侧构建应用到应用的通信隧道&#xff0c;用于收发应用自定义的报文消息以及文件。实现手机应用和穿戴设备应用间的交互&#xff0c;为用户提供分布式场景和体验。比如手机应用发送音频文件到穿戴设备侧应用&#xff0c;实现在穿戴设备侧应用上播放音乐&#…

BUG——IMX6ULL编译正点原子Linux内核报错

最初编译的是正点原子改过的Linux内核&#xff0c;可能是版本问题&#xff0c;一直报错&#xff0c;无法成功编译。然后换成NXP官方Linux内核6.6版本&#xff0c;初始编译虽然也报各种错&#xff0c;但都是缺少库或相关工具&#xff0c;全部安装后就可以成功编译出镜像了&#…

Leetcode 740. 删除并获得点数

原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums &#xff0c;你可以对它进行一些操作。 每次操作中&#xff0c;选择任意一个 nums[i] &#xff0c;删除它并获得 nums[i] 的点数。之后&#xff0c;你必须删除 所有 等于 nums[i] - 1 和…

cscode搭建vue项目

创建前安装环境 ctrlj弹出终端 window需要管理员运行并且授权 node -v # 显示版本号&#xff0c;说明 node 已经装好 npm -v # 显示版本号&#xff0c;说明 npm 可以使用 # 安装cnpm npm install -g cnpm --registryhttps://registry.npm.taobao.org cnpm -v # 显示版本号&a…

【hot100-java】【合并两个有序链表】

记忆中&#xff0c;两个指针合并即可。 建立哨兵节点dum /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { t…

Docker更换阿里容器镜像源

以Mac为例&#xff0c; 一、获取阿里容器镜像加速器地址 访问阿里云官网https://cn.aliyun.com/ 登录阿里云&#xff0c;没有账号就注册一个 登录完成后在搜索框搜索&#xff0c;容器镜像服务&#xff0c;并打开 点击管理控制台&#xff0c;进入管理控制台 左侧点击镜像加速…

【Python入门】20个Python自动化脚本,解放双手、事半功倍

如果你正在学习Python&#xff0c;那么你需要的话可以&#xff0c;点击这里&#x1f449;Python重磅福利&#xff1a;入门&进阶全套学习资料、电子书、软件包、项目源码等等免费分享&#xff01; 在当今的快节奏工作环境中&#xff0c;自动化不再是一种奢侈&#xff0c;而是…

下一代性能怪兽RTX 5090最新规格更新与Blackwell架构解析

据悉&#xff0c;目前各家AIC厂商已经陆续收到NVIDIA的相关资料&#xff0c;RTX 5090、RTX 5080已经正式进入开案阶段&#xff0c;也就是厂商们开始设计各自的产品方案了。不出意外&#xff0c;年初的CES 2025上会看到RTX 5090/5080的发布。 作为NVIDIA的新一代GPU&#xff0c…

【车联网安全】车端知识调研

一、CAN总线&#xff1a; 1、定义&#xff1a; CAN 总线相当于汽车的神经网络&#xff0c;连接车内各控制系统,其通信采用广播机制&#xff0c;各连接部件均可收发控制消息&#xff0c;通信效率高&#xff0c;可确保通信实时性。当前市场上的汽车至少拥有一个CAN网络&#xff0…

如何进行“服务器内部错误”的诊断 | OceanBase诊断案例

本文作者&#xff1a;任仲禹&#xff0c;爱可生数据库高级工程师&#xff0c;擅长故障分析和性能优化。 的OMS迁移工具具备丰富的功能。但在实际运维场景中&#xff0c;我们可能会遇到各种问题&#xff0c;其中“服务器内部错误”便是一个较为棘手的问题&#xff0c;因为界面上…

【易上手快捷开发新框架技术】nicegui标签组件lable用法庖丁解牛深度解读和示例源代码IDE运行和调试通过截图为证

传奇开心果微博文系列 序言一、标签组件lable最基本用法示例1.在网页上显示出 Hello World 的标签示例2. 使用 style 参数改变标签样式示例 二、标签组件lable更多用法示例1. 添加按钮动态修改标签文字2. 点击按钮动态改变标签内容、颜色、大小和粗细示例代码3. 添加开关组件动…

美图AI短片创作工具MOKI全面开放 支持生成配乐、细节修改

人工智能 - Ai工具集 - 集合全球ai人工智能软件的工具箱网站 美图公司近日宣布&#xff0c;其研发的AI短片创作工具MOKI已正式向所有用户开放。这款专注于AI短片创作的工具&#xff0c;提供了包括动画短片、网文短剧等多种类型视频内容的生成能力&#xff0c;致力于为用户带来…

linux-CMake

linux-CMake 1.安装CMake工具2.单个源文件3.多个源文件4.生成库文件5.将源文件组织到不同的目录下6.可执行文件和库文件放置到单独的目录下7.常见的命令 CMake使用。 1.安装CMake工具 sudo apt-get install cmake2.单个源文件 1.先在文件夹里创建两个文件&#xff1a;main.c&…

Vscode超好看的渐变主题插件

样式效果&#xff1a; 插件使用方法&#xff1a; 然后重启&#xff0c;之后会显示vccode损坏&#xff0c;不用理会&#xff0c;因为这个插件是更改了应用内部代码&#xff0c;直接不再显示即可。

cesium实战代码

代码中有一点bug还没改 cesium地球 地形+地形 <html lang="en"><head><style>.cesium-animation-rectButton .cesium-animation-buttonGlow {filter: url(#animation_blurred); }.cesium-animation-rectButton .cesium-animation-buttonMain {fil…

Win32打开UWP应用

最近无意间发现Windows里一个神奇的文件夹。 shell:appsfolder 运行打开 这个文件夹后&#xff0c;你可以看到本机安装的所有应用程序。 我觉得这个挺方便的&#xff0c;所以做了一个简单的appFolderDialog包给C#用 项目地址&#xff1a;https://github.com/TianXiaTech/App…