当前位置: 首页 > news >正文

Android14 InputManager-InputReader的处理

IMS启动时会调用InputReader.start()方法

InputReader.cpp
status_t InputReader::start() {if (mThread) {return ALREADY_EXISTS;}mThread = std::make_unique<InputThread>("InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });return OK;
}

当线程开始运行后,将会在内建的线程循环中不断地调用threadLoop(),直到此函数返回false,则退出线程循环,从而结束线程。

InputReader的一次线程循环的工作思路非常清晰,一共三步:

□首先从EventHub中抽取未处理的事件列表。这些事件分为两类,一类是从设备节点中读取的原始输入事件,另一类则是输入设备可用性变化事件,简称为设备事件。

□通过processEventsLocked()对事件进行处理。对于设备事件,此函数对根据设备的可用性加载或移除设备对应的配置信息。对于原始输入事件,则在进行转译、封装与加工后将结果暂存到mQueuedListener中。

□所有事件处理完毕后,调用mQueuedListener.flush()将所有暂存的输入事件一次性地交付给InputDispatcher。

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 lockstd::scoped_lock _l(mLock);oldGeneration = mGeneration;timeoutMillis = -1;auto changes = mConfigurationChangesToRefresh;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 lockstd::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis);{ // acquire lockstd::scoped_lock _l(mLock);mReaderIsAliveCondition.notify_all();
// 如果有事件信息,调用processEventsLocked()函数对事件进行加工处理if (!events.empty()) {notifyArgs += processEventsLocked(events.data(), events.size());}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);}}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();
}

processEventsLocked()会分别处理原始输入事件与设备增删事件。

对于原始输入事件,由于EventHub会将属于同一输入设备的原始输入事件放在一起,因此processEventsLocked()可以使processEventsForDeviceLocked()同时处理来自同一输入设备的一批事件。

std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {std::list<NotifyArgs> out;for (const RawEvent* rawEvent = rawEvents; count;) {int32_t type = rawEvent->type;size_t batchSize = 1;if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {int32_t deviceId = rawEvent->deviceId;while (batchSize < count) {if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||rawEvent[batchSize].deviceId != deviceId) {break;}batchSize += 1;}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 happenbreak;}}count -= batchSize;rawEvent += batchSize;}return out;
}
std::list<NotifyArgs> InputReader::processEventsForDeviceLocked(int32_t eventHubId,const RawEvent* rawEvents,size_t count) {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 {};}return device->process(rawEvents, count);
}
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) {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;out += reset(rawEvent->when);} else {for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {out += mapper.process(rawEvent);});}--count;}return out;
}

InputMapper是InputReader中实际进行原始输入事件加工的场所,它有一系列的子类,分别用于加工不同类型的原始输入事件。而InputDevice的process()函数使用InputMapper的方式是一个简化了的职责链(chain of responsibility)设计模式。InputDevice不需要知道哪一个InputMapper可以处理一个原始输入事件,只须将一个事件逐个交给每一个InputMapper尝试处理,如果InputMapper可以接受这个事件则处理之,否则什么都不做。

根据设备的类型,将设备做如下分配

InputMapper的种类实在很多,对所有类型都进行详细分析并不现实。选择KeyboardInputMapper和MultiTouchInputMapper两个常用并且具有代表性的InputMapper进行探讨

键盘事件的处理

KeyboardInputMapper的process()函数比较简单

□通过processKey()按键事件做进一步处理。

mQueuedListener.flush();

根据扫描码获取虚拟键值以及功能值后,KeyboardInputMapper::process()调用了processKey()函数对按键事件做进一步处理。

将键盘信息转化为NotifyKeyArgs,返回到loopOnce的loopOnce中,通知事件分发

触摸事件的处理

触摸事件的处理

std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent* rawEvent) {std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);mMultiTouchMotionAccumulator.process(rawEvent);return out;
}

事件处理完成后回到最初的

mQueuedListener.flush(); // 将事件交给inputDispatcher的过程

 

InputDispatcher继承了InputDispatcherInterface接口
class InputDispatcher : public android::InputDispatcherInterface {
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);
}

以motion事件为例

void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) {if (debugInboundEventDetails()) {ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, ""displayId=%" PRId32 ", policyFlags=0x%x, ""action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, ""edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, ""yCursorPosition=%f, downTime=%" PRId64,args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),args.displayId, args.policyFlags, MotionEvent::actionToString(args.action).c_str(),args.actionButton, args.flags, args.metaState, args.buttonState, args.edgeFlags,args.xPrecision, args.yPrecision, args.xCursorPosition, args.yCursorPosition,args.downTime);for (uint32_t i = 0; i < args.pointerCount; i++) {ALOGD("  Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, ""touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f",i, args.pointerProperties[i].id,ftl::enum_string(args.pointerProperties[i].toolType).c_str(),args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));}}Result<void> motionCheck = validateMotionEvent(args.action, args.actionButton,args.pointerCount, args.pointerProperties);if (!motionCheck.ok()) {LOG(ERROR) << "Invalid event: " << args.dump() << "; reason: " << motionCheck.error();return;}uint32_t policyFlags = args.policyFlags;policyFlags |= POLICY_FLAG_TRUSTED;android::base::Timer t;mPolicy.interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags);if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",std::to_string(t.duration().count()).c_str());}bool needWake = false;{ // acquire lockmLock.lock();if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) {// Set the flag anyway if we already have an ongoing gesture. That would allow us to// complete the processing of the current stroke.const auto touchStateIt = mTouchStatesByDisplay.find(args.displayId);if (touchStateIt != mTouchStatesByDisplay.end()) {const TouchState& touchState = touchStateIt->second;if (touchState.deviceId == args.deviceId && touchState.isDown()) {policyFlags |= POLICY_FLAG_PASS_TO_USER;}}}if (shouldSendMotionToInputFilterLocked(args)) {ui::Transform displayTransform;if (const auto it = mDisplayInfos.find(args.displayId); it != mDisplayInfos.end()) {displayTransform = it->second.transform;}mLock.unlock();MotionEvent event;event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC,args.action, args.actionButton, args.flags, args.edgeFlags,args.metaState, args.buttonState, args.classification,displayTransform, args.xPrecision, args.yPrecision,args.xCursorPosition, args.yCursorPosition, displayTransform,args.downTime, args.eventTime, args.pointerCount,args.pointerProperties, args.pointerCoords);policyFlags |= POLICY_FLAG_FILTERED;if (!mPolicy.filterInputEvent(event, policyFlags)) {return; // event was consumed by the filter}mLock.lock();}// Just enqueue a new motion event.std::unique_ptr<MotionEntry> newEntry =std::make_unique<MotionEntry>(args.id, args.eventTime, args.deviceId, args.source,args.displayId, policyFlags, args.action,args.actionButton, args.flags, args.metaState,args.buttonState, args.classification, args.edgeFlags,args.xPrecision, args.yPrecision,args.xCursorPosition, args.yCursorPosition,args.downTime, args.pointerCount,args.pointerProperties, args.pointerCoords);if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER &&!mInputFilterEnabled) {const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN;mLatencyTracker.trackListener(args.id, isDown, args.eventTime, args.readTime);}needWake = enqueueInboundEventLocked(std::move(newEntry));mLock.unlock();} // release lockif (needWake) {mLooper->wake();}
}

到达InputDispatcher的Motion事件被保存在MotionEntry类中

bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {bool needWake = mInboundQueue.empty();mInboundQueue.push_back(std::move(newEntry));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.");// Optimize app switch latency.// If the application takes too long to catch up then we drop all events preceding// the app switch key.const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);if (isAppSwitchKeyEvent(keyEntry)) {if (keyEntry.action == AKEY_EVENT_ACTION_DOWN) {mAppSwitchSawKeyDown = true;} else if (keyEntry.action == AKEY_EVENT_ACTION_UP) {if (mAppSwitchSawKeyDown) {if (DEBUG_APP_SWITCH) {ALOGD("App switch is pending!");}mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT;mAppSwitchSawKeyDown = false;needWake = true;}}}// 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) {KeyEntry& pendingKey = static_cast<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.");if (shouldPruneInboundQueueLocked(static_cast<MotionEntry&>(entry))) {mNextUnblockedEvent = mInboundQueue.back();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 dobreak;}}return needWake;
}

通过这两个函数可以看出,到达InputDispatcher的Motion事件被保存在MotionEntry类中,然后排在mInboundQueue列表的队尾,这个mInboundQueue就是InputDispatcher的派发队列。MotionEntry是EventEntry的一个子类,保存了Motion事件的信息。Key事件也有一个KeyEntry与之对应。EventEntry是输入事件在InputDispatcher中的存在形式。另外,由于InputDispatcher在没有事件可以派发时(mInboundQueue为空),将会进入休眠状态,因此在将事件放入派发队列时,需要将派发线程唤醒。

上述过程运行在inputReader线程中,至此inputReader的处理完成

相关文章:

Android14 InputManager-InputReader的处理

IMS启动时会调用InputReader.start()方法 InputReader.cpp status_t InputReader::start() {if (mThread) {return ALREADY_EXISTS;}mThread std::make_unique<InputThread>("InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });…...

web前端安全性——JSONP劫持

1、JSONP概念 JSONP(JSON with Padding)是JSON的一种“使用模式”&#xff0c;可用于解决主流浏览器的跨域数据访问的问题。由于同源策略&#xff0c;协议IP端口有任意不同都会导致请求跨域&#xff0c;而HTML的script元素是一个例外。利用script元素的这个开放策略&#xff0…...

从零开始学HCIA之广域网技术03

1、LCP中包含的报文类型 &#xff08;1&#xff09;Configure-Request&#xff08;配置请求&#xff09;&#xff0c;链路层协商过程中发送的第一个报文&#xff0c;该报文表明点对点双方开始进行链路层参数的协商。 &#xff08;2&#xff09; Configure-Ack&#xff08;配置…...

AI推介-大语言模型LLMs论文速览(arXiv方向):2024.01.01-2024.01.10

1.Pre-trained Large Language Models for Financial Sentiment Analysis 标题:用于金融情感分析的预训练大型语言模型 author:Wei Luo, Dihong Gong date Time:2024-01-10 paper pdf:http://arxiv.org/pdf/2401.05215v1 摘要&#xff1a; 金融情感分析是指将金融文本内容划分…...

Redis降低内存占用(二)分片结构

一、分区方法&#xff1a; 分片&#xff0c;也称为分区。Redis提供了多种分区实现方案: 1、哈希分区 2、区间分区 3、一致性哈希分区 4、虚拟分区 5、LUA脚本实现分片 二、...

vue大文件读取部分内容,避免重复加载大文件,造成流量浪费

使用场景&#xff1a;项目点云地图是pcd文件&#xff0c;但是文件可能上百兆&#xff0c;我需要获取到文件中的版本信息&#xff0c;跟本地的缓存文件做比较&#xff0c;如果不一致&#xff0c;才会加载整个文件。从而节省流量。 避免重复加载整个“.pcd文件&#xff0c;以最大…...

5G网络RedCap

RedCap&#xff1a;RedCap&#xff08;Reduced Capability&#xff09;&#xff0c;即“降低能力”。它是3GPP在5G R17阶段&#xff0c;针对速率、时延要求不高的5G应用场景&#xff0c;专门推出的一种新技术标准协议&#xff0c;旨在全面提升5G网络质量和覆盖率&#xff0c;也…...

vue+springboot登录与注册功能的实现

①首先写一个登录页面 <template> <div style"background-color: #42b983;display: flex;align-items: center;justify-content: center;height: 100vh"><div style"background-color: white;display: flex;width: 50%;height: 50%;overflow: h…...

数据结构D3作业

1. 2. 按位插入 void insert_pos(seq_p L,datatype num,int pos) { if(LNULL) { printf("入参为空&#xff0c;请检查\n"); return; } if(seq_full(L)1) { printf("表已满&#xff0c;不能插入\n"); …...

Spring框架@Autowired注解进行字段时,使用父类类型接收子类变量,可以注入成功吗?(@Autowired源码跟踪)

一、 前言 平常我们在使用spring框架开发项目过程中&#xff0c;会使用Autowired注解进行属性依赖注入&#xff0c;一般我们都是声明接口类型来接收接口实现变量&#xff0c;那么使用父类类型接收子类变量&#xff0c;可以注入成功吗&#xff1f;答案是肯定可以的&#xff01;…...

【springblade】springblade(bladeX) 数据权限失效原因分析

文章目录 数据权限接口权限 前言&#xff1a;最近博主在按照bladeX官方文档 配置数据权限 结果发现失效了&#xff0c;网上搜了一下没找到合适的答案&#xff0c;本着求人不如求己的精神&#xff0c;自己调试了一下发现了问题所在&#xff0c;也大致看了一下bladeX的权限逻辑。…...

单例模式的几种实现方式

在Java中&#xff0c;实现单例模式主要有几种方式&#xff1a;懒汉式、饿汉式、双重检查锁定、静态内部类和枚举。每种方式都有其特点和适用场景。 1. 饿汉式&#xff08;线程安全&#xff09; 饿汉式是最简单的一种实现方式&#xff0c;通过静态初始化实例&#xff0c;保证了…...

鸿蒙OS运行报错 ‘ToDoListItem({ item })‘ does not meet UI component syntax.

在学习harmonyOS时&#xff0c;原本是好好运行的。但是突然报错 ToDoListItem({ item }) does not meet UI component syntax. 一脸懵逼&#xff0c;以为是自己语法问题检查了半天也没问题。 网上搜索了一下&#xff0c;说把多余的js\map文件删除就行 才发现我的 鸿蒙的开…...

React18源码: reconciler执行流程

reconciler执行流程 1 &#xff09;概述 此处先归纳一下react-reconciler包的主要作用&#xff0c;将主要功能分为4个方面&#xff1a; 输入&#xff1a;暴露api函数&#xff08;如&#xff1a;scheduleUpdateOnFiber&#xff09;, 供给其他包&#xff08;如react包&#xff0…...

mapbox面图层标注

mapbox并没有一个属性类似于’text-field’的symbol图层的直接可以标注的办法&#xff0c;这里笔者提供两种其他的面图层标注的办法用来大家参考 效果图 方案一 把面图层当做点图层直接展示 在mapbox里面&#xff0c;面图层是可以直接渲染成线图层和点图层的&#xff0c;这里…...

MySQL|MySQL基础(求知讲堂-学习笔记【详】)

MySQL基础 目录 MySQL基础一、 MySQL的结构二、 管理数据库1&#xff09;查询所有的数据库2&#xff09;创建数据库3&#xff09;修改数据库的字符编码4&#xff09;删除数据库5&#xff09;切换操作的数据库 三、表的概念四、字段的数据类型4.1 整型4.2 浮点型(float和double)…...

10.docker exec -it /bin/bash报错解决、sh与bash区别

报错 进入容器时&#xff0c;报如下错误 dockeruserdell-PowerEdge-R740:~$ docker exec -it daf2 /bin/bash OCI runtime exec failed: exec failed: unable to start container process: exec: "/bin/bash": stat /bin/bash: no such file or directory: unknown…...

查询数据库的编码集Oracle,MySQL

1、查询数据库的编码集Oracle,MySQL 1.1、oracle select * from v$nls_parameters where parameterNLS_CHARACTERSET; 查询版本&#xff1a;SELECT * FROM v$version 2、MySQL编码集 SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SC…...

电商数据采集+跨境电商|API电商数据采集接口洞悉数字新零售发展

随着全球经济一体化和电子商务的快速发展&#xff0c;网络购物的需求日益增加。不断涌现的电商企业使得行业的竞争情况愈演愈烈。在这种情况下&#xff0c;企业不仅要加大经营力度&#xff0c;还要在自己的基础设施和技术上持续投入&#xff0c;才能更好的适应市场和消费习惯。…...

linux之用户和用户组

文章目录 一、简介1.1 用户1.2 用户组1.3 UID和GID1.4 用户账户分类 二、用户2.1 添加新的用户账号&#xff1a;useradd2.2 删除账号&#xff1a;userdel2.3 修改账号&#xff1a;usermod(modmodify)2.4 用户口令的管理:passwd2.5 切换用户&#xff1a;su 三、用户组3.1 增加一…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》

&#x1f9e0; LangChain 中 TextSplitter 的使用详解&#xff1a;从基础到进阶&#xff08;附代码&#xff09; 一、前言 在处理大规模文本数据时&#xff0c;特别是在构建知识库或进行大模型训练与推理时&#xff0c;文本切分&#xff08;Text Splitting&#xff09; 是一个…...

Gitlab + Jenkins 实现 CICD

CICD 是持续集成&#xff08;Continuous Integration, CI&#xff09;和持续交付/部署&#xff08;Continuous Delivery/Deployment, CD&#xff09;的缩写&#xff0c;是现代软件开发中的一种自动化流程实践。下面介绍 Web 项目如何在代码提交到 Gitlab 后&#xff0c;自动发布…...