Android14 InputManager-InputReader的处理

news2024/11/18 12:18:27

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 lock
        std::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 lock

    std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis);

    { // acquire lock
        std::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 happen
                    break;
            }
        }
        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 lock
        mLock.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 lock

    if (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 do
            break;
        }
    }

    return needWake;
}

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

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

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

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

相关文章

第3.4章:StarRocks数据导入-Routine Load

注&#xff1a;本篇文章阐述的是StarRocks-3.2版本的Routine Load导入机制 一、概述 Routine Load&#xff08;例行导入&#xff09;支持用户提交一个常驻的导入任务&#xff0c;可以将消息流存储在 Kafka 的Topic中&#xff0c;通过订阅Topic 中的全部或部分分区的消息&#…

什么是EMC电磁兼容性测试?

摘要: 电磁兼容性测试&#xff08;Electromagnetic Compatibility Testing&#xff0c;简称EMC测试&#xff09;是对电子产品在电磁场方面干扰大小&#xff08;EMI&#xff09;和抗干扰能力&#xff08;EMS&#xff09;的综合评定。其目的是检测电器产品所产生的电磁辐射对人体…

代码随想录Leetcode 343. 整数拆分

题目&#xff1a; 代码(首刷看解析 2024年2月21日&#xff09;&#xff1a; dp[i]表示i所能拆分的最大乘积&#xff0c;则dp[i] 与dp[i - 1]的递推公式是&#xff1a; max( 1~n * dp[n ~ 1]) class Solution { public:int integerBreak(int n) {vector<int> dp(n 1);dp…

Python: argparse基本用法

Python: argparse基本用法 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;【Matplotlib之旅&#xff1a;零基础精通数据可视化】 &#x1f3c6;&#x1f3c6;关注博主&#xff0c;随时获取更多关于深度学习、PyTorch、Python领域的优质内容…

快速清理_卸载docker_找到不用的进程_centos磁盘爆满_清理磁盘---Linux工作笔记071

查看大文件,并且按照大小排名 cd / | du -h |sort -hr|head -30 可以看到根据不用的结果进行删除 可以看到在/data/dict目录很大,里面的都可以删除 然后再去卸载docker,要不然,没有磁盘是卸载不了的 systemctl stop docker systemctl stop docker.socket yum remove docker-…

DataX - 全量数据同步工具

前言 今天是2024-2-21&#xff0c;农历正月十二&#xff0c;相信今天开始是新的阶段&#xff0c;尽管它不是新的周一、某月一日、某年第一天&#xff0c;尽管我是一个很讲究仪式感的人。新年刚过去 12 天&#xff0c;再过 3 天就开学咯&#xff0c;开学之后我的大学时光就进入了…

JavaSE匿名对象 , 继承 , 抽象类

文章目录 1 面向对象回顾面向对象的核心思想是什么 ?现有的类还是先有的对象 ?Java类的创建 ?类中的组成成分 ?创建对象所使用的关键字 ?创建对象的格式 ?调用对象的成员 ?定义构造方法的格式 ?定义构造方法的特点 ?构造方法的作用 ?面向对象三大特征是什么 ?封装的…

java基础之 SPI机制

SPI机制说明 什么是SPI Service Provider Interface 机制是Java提供的一套用来被第三方实现或扩展的API&#xff0c;他可以用来启用框架扩展和替换组件。通过“基于接口的编程 策略模式 配置文件”组合实现的动态加载机制。SPI机制为某个接口寻找服务实现的机制&#xff0c;…

Autosar-Mcal配置详解-MCU

3.6.1创建、配置RAM 1)创建RAM配置 2)配置RAM 以F1KM R7F7016533ABG为例,它的local RAM有512K, global RAM 192K,Retention RAM 64K. Local RAM: local RAM就是程序平常使用的RAM,在DeepStop模式下内容会丢失。 Global RAM:主要用于DMA的源地址和目的地址使用,在Dee…

2012及其以上系统修改服务器密码指南

修改服务器密码指南,目前介绍两种不同的方案 方法一 指令式 winR键 弹出运行框里输入 cmd 点击确认或者右下角开始程序里面的点开运行 2.在弹出框里手动输入以下一组文字&#xff1a;net user administrator 123456 框内无法粘贴 需要手动输入 其中administrator 是用…

上传回显图片

<!-- 父页面--><el-dialog title"直播详情" :visible.sync"dialogFormVisible" append-to-body:close-on-click-modal"false" width"50%" close"dialogClose"><editUserVideo v-if"dialogFormVisible&q…

【Node.js】介绍、下载及安装

目录 一、什么是 Node.js 二、Node.js下载 下载方式1&#xff1a;直接在首页下载&#xff08;下载的是.msi后缀的安装包&#xff09; 下载方式2&#xff1a;点击官网顶上的DOWNLOAD 三、Node.js安装 .zip后缀的安装步骤 .msi后缀的安装步骤 一、什么是 Node.js Node.js …

计算以10为底的对数 math.log10(x)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算以10为底的对数 math.log10(x) [太阳]选择题 以下代码的输出结果中正确的是? import math print("【执行】math.log10(10)") print(math.log10(10)) print("【执行】math…

基于 QUIC 协议的 HTTP/3 正式发布!

近期&#xff0c;超文本传输协议新版本 HTTP/3 RFC 文档&#xff0c;已由互联网工程任务组&#xff08;IETF&#xff09;对外发布。HTTP/3 全称为 HTTP-over-QUIC&#xff0c;指在 QUIC&#xff08;Quick UDP Internet Connections, 快速 UDP 互联网连接&#xff09;上映射 HTT…

OpenTiny Vue 组件库适配微前端可能遇到的4个问题

本文由体验技术团队 TinyVue 项目成员岑灌铭同学创作。 前言 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略&#xff0c;每个应用可以选择不同的技术栈&#xff0c;独立开发、独立部署。 TinyVue组件库的跨技术栈能力与微前端十…

十三、图像像素值统计

项目功能实现&#xff1a;对一张图像进行统计最大、最小像素值、均差以及方差的值 按照之前的博文结构来&#xff0c;这里就不在赘述了 一、头文件 pixel_statistic.h #pragma once#include<opencv2/opencv.hpp>using namespace cv;class Pixel_Statistic { public:vo…

camunda源代码编译运行(三):验证camunda API接口功能

接上一篇文章&#xff1a;camunda源代码编译运行&#xff08;二&#xff09;&#xff1a;构建并运行camunda源代码工程 4.1、发布流程模型 先通过camunda的流程设计器设计一个流程&#xff0c;命名为&#xff1a;UserTask Flow1&#xff0c;然后发布流程&#xff0c;发布流程…

HUD阳光倒灌实验TFT温升实验太阳光模拟器

随着汽车行业的不断发展&#xff0c;车辆的智能化程度越来越高&#xff0c;而HUD抗干扰太阳光模拟器作为一种新型的解决驾驶员视线问题的方案&#xff0c;引起了人们的广泛关注。它能够有效地解决驾驶员在阳光强烈或者夜间光线不足时的视线问题&#xff0c;提高了驾驶的安全性和…

Java写爱心

突然想用Java写一个爱心&#xff0c;程序运行效果如下&#xff1a; 话不多说&#xff0c;上代码 ! public class LovingHeart {/*** 判断坐标是否在爱心范围内*/private static boolean inHeart(float x,float y){float ax*xy*y-1;return a*a*a-x*x*y*y*y<0.0f;}/*** 爱心…

【Vuforia+Unity】AR05-实物3D模型识别功能实现

对于3D物体的识别&#xff0c;可以是虚拟的也可以是实物的&#xff0c;但是对于虚拟的三维模型意义不大&#xff0c;我们完全可以把三维模型放在屏幕上截一张图&#xff0c;以图片识别的方式召唤数字内容&#xff0c;不过在虚拟现实中或许有用。 因此本文探讨的技术路线主要是…