Android12 显示框架之Transaction----server端

news2025/1/11 8:01:06

目录:Android显示终极宝典

上篇讲完了在client端Transaction的内容,最后调用setTransactionState()把所有的参数都交给了surfaceflinger,那么任务就交给server来完成了。本节我们一起接着看看下面的内容。

setTransactionState()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
status_t SurfaceFlinger::setTransactionState(
        const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
        const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
        const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
        bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
        const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
    ATRACE_CALL();

    uint32_t permissions =
            callingThreadHasUnscopedSurfaceFlingerAccess() ? Permission::ACCESS_SURFACE_FLINGER : 0;
    // Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER
    // permissions.
    if ((permissions & Permission::ACCESS_SURFACE_FLINGER) ||
        callingThreadHasRotateSurfaceFlingerAccess()) {
        permissions |= Permission::ROTATE_SURFACE_FLINGER;
    }

    if (!(permissions & Permission::ACCESS_SURFACE_FLINGER) &&
        (flags & (eEarlyWakeupStart | eEarlyWakeupEnd))) {
        ALOGE("Only WindowManager is allowed to use eEarlyWakeup[Start|End] flags");
        flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd);
    }

    const int64_t postTime = systemTime();

    IPCThreadState* ipc = IPCThreadState::self();
    const int originPid = ipc->getCallingPid();
    const int originUid = ipc->getCallingUid();
    TransactionState state{frameTimelineInfo,  states,
                           displays,           flags,
                           applyToken,         inputWindowCommands,
                           desiredPresentTime, isAutoTimestamp,
                           uncacheBuffer,      postTime,
                           permissions,        hasListenerCallbacks,
                           listenerCallbacks,  originPid,
                           originUid,          transactionId};

    // Check for incoming buffer updates and increment the pending buffer count.
    state.traverseStatesWithBuffers([&](const layer_state_t& state) {
        mBufferCountTracker.increment(state.surface->localBinder());
    });
    queueTransaction(state);

    // Check the pending state to make sure the transaction is synchronous.
    if (state.transactionCommittedSignal) {
        waitForSynchronousTransaction(*state.transactionCommittedSignal);
    }

    return NO_ERROR;
}

这段代码主要看queueTransaction(),其他内容大概知道怎么回事就可以了。client传进来的参数会和其他临时创建的变量一起被构建成一个TransactionState,将其送入queueTransaction()基于此来执行后续流程。

queueTransaction()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::queueTransaction(TransactionState& state) {
    Mutex::Autolock _l(mQueueLock);

    // If its TransactionQueue already has a pending TransactionState or if it is pending
    auto itr = mPendingTransactionQueues.find(state.applyToken);
    // if this is an animation frame, wait until prior animation frame has
    // been applied by SF
    if (state.flags & eAnimation) {
        while (itr != mPendingTransactionQueues.end()) {
            status_t err = mTransactionQueueCV.waitRelative(mQueueLock, s2ns(5));
            if (CC_UNLIKELY(err != NO_ERROR)) {
                ALOGW_IF(err == TIMED_OUT,
                         "setTransactionState timed out "
                         "waiting for animation frame to apply");
                break;
            }
            itr = mPendingTransactionQueues.find(state.applyToken);
        }
    }

    // Generate a CountDownLatch pending state if this is a synchronous transaction.
    if ((state.flags & eSynchronous) || state.inputWindowCommands.syncInputWindows) {
        state.transactionCommittedSignal = std::make_shared<CountDownLatch>(
                (state.inputWindowCommands.syncInputWindows
                         ? (CountDownLatch::eSyncInputWindows | CountDownLatch::eSyncTransaction)
                         : CountDownLatch::eSyncTransaction));
    }

    mTransactionQueue.emplace(state);
    ATRACE_INT("TransactionQueue", mTransactionQueue.size());

    const auto schedule = [](uint32_t flags) {
        if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
        if (flags & eEarlyWakeupStart) return TransactionSchedule::EarlyStart;
        return TransactionSchedule::Late;
    }(state.flags);

    setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken);
}

这段代码主要看三点:

  • mTransactionQueue.emplace(state):将state存储起来,后面在另一个线程处理。
  • 获取schedule值:这个一般情况比如本地播放等结果为TransactionSchedule::Late,EarlyStart和EarlyEnd则是WMS在切换窗口时设置,这里不考虑这种特殊case。
  • setTransactionFlags():根据flags及mTransactionFlags旧值决定本次是否发起invalidate消息。该函数内的modulateVsync()在当前内容中暂时不关心。
//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::signalTransaction() {
    mScheduler->resetIdleTimer();
    mPowerAdvisor.notifyDisplayUpdateImminent();
    mEventQueue->invalidate();
}

假设本次执行signalTransaction(),跑完mEventQueue->invalidate()之后,后面的执行流程是个什么样子呢?简单画图会比较直白方便大家理解一些:

具体代码大家可以依据图中连线上的接口名称去看code,这里简单描述下过程:signalTransaction()执行之后,代码逻辑会经有MessageQueue+VSyncDispatch+Timer这三者,最终把Transaction需要处理的信息送达surfaceflinger主线程进行处理。

handleMessageTransaction()

接下来直接看handleMessageTransaction():

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
bool SurfaceFlinger::handleMessageTransaction() {
    ATRACE_CALL();

    if (getTransactionFlags(eTransactionFlushNeeded)) {
        flushTransactionQueues();
    }
    uint32_t transactionFlags = peekTransactionFlags();
    bool runHandleTransaction =
            ((transactionFlags & (~eTransactionFlushNeeded)) != 0) || mForceTraversal;

    if (runHandleTransaction) {
        handleTransaction(eTransactionMask);
    }

    if (transactionFlushNeeded()) {
        setTransactionFlags(eTransactionFlushNeeded);
    }

    return runHandleTransaction;
}

这里注意getTransactionFlags()函数传入的eTransactionFlushNeeded,也就是说之前必须设置过eTransactionFlushNeeded标志位才会真正的执行flushTransactionQueues(),执行完以后还要清除掉mTransactionFlags中的eTransactionFlushNeeded标志位。

flushTransactionQueues()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::flushTransactionQueues() {
    // to prevent onHandleDestroyed from being called while the lock is held,
    // we must keep a copy of the transactions (specifically the composer
    // states) around outside the scope of the lock
    std::vector<const TransactionState> transactions;
    // Layer handles that have transactions with buffers that are ready to be applied.
    std::unordered_set<sp<IBinder>, ISurfaceComposer::SpHash<IBinder>> bufferLayersReadyToPresent;
    {
        Mutex::Autolock _l(mStateLock);
        {
            Mutex::Autolock _l(mQueueLock);
            // Collect transactions from pending transaction queue.
            auto it = mPendingTransactionQueues.begin();
            while (it != mPendingTransactionQueues.end()) {
                auto& [applyToken, transactionQueue] = *it;

                while (!transactionQueue.empty()) {
                    auto& transaction = transactionQueue.front();
                    if (!transactionIsReadyToBeApplied(transaction.frameTimelineInfo,
                                                       transaction.isAutoTimestamp,
                                                       transaction.desiredPresentTime,
                                                       transaction.originUid, transaction.states,
                                                       bufferLayersReadyToPresent)) {
                        setTransactionFlags(eTransactionFlushNeeded);
                        break;
                    }
                    transaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
                        bufferLayersReadyToPresent.insert(state.surface);
                    });
                    transactions.emplace_back(std::move(transaction));
                    transactionQueue.pop();
                }

                if (transactionQueue.empty()) {
                    it = mPendingTransactionQueues.erase(it);
                    mTransactionQueueCV.broadcast();
                } else {
                    it = std::next(it, 1);
                }
            }

            // Collect transactions from current transaction queue or queue to pending transactions.
            // Case 1: push to pending when transactionIsReadyToBeApplied is false.
            // Case 2: push to pending when there exist a pending queue.
            // Case 3: others are ready to apply.
            while (!mTransactionQueue.empty()) {
                auto& transaction = mTransactionQueue.front();
                bool pendingTransactions = mPendingTransactionQueues.find(transaction.applyToken) !=
                        mPendingTransactionQueues.end();
                if (pendingTransactions ||
                    !transactionIsReadyToBeApplied(transaction.frameTimelineInfo,
                                                   transaction.isAutoTimestamp,
                                                   transaction.desiredPresentTime,
                                                   transaction.originUid, transaction.states,
                                                   bufferLayersReadyToPresent)) {
                    mPendingTransactionQueues[transaction.applyToken].push(std::move(transaction));
                } else {
                    transaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
                        bufferLayersReadyToPresent.insert(state.surface);
                    });
                    transactions.emplace_back(std::move(transaction));
                }
                mTransactionQueue.pop();
                ATRACE_INT("TransactionQueue", mTransactionQueue.size());
            }
        }

        // Now apply all transactions.
        for (const auto& transaction : transactions) {
            applyTransactionState(transaction.frameTimelineInfo, transaction.states,
                                  transaction.displays, transaction.flags,
                                  transaction.inputWindowCommands, transaction.desiredPresentTime,
                                  transaction.isAutoTimestamp, transaction.buffer,
                                  transaction.postTime, transaction.permissions,
                                  transaction.hasListenerCallbacks, transaction.listenerCallbacks,
                                  transaction.originPid, transaction.originUid, transaction.id);
            if (transaction.transactionCommittedSignal) {
                mTransactionCommittedSignals.emplace_back(
                        std::move(transaction.transactionCommittedSignal));
            }
        }
    }
}

第一个while循环检查mPendingTransactionQueues中的transaction是否已经准备好被apply,如果是则记录到临时transactions的vector中,否则重新设置eTransactionFlushNeeded。

第二个while循环则是对queueTransaction()中新添加的mTransactionQueue遍历检查是否可以被apply,如果可以则将其移入transactions中,否则放入mPendingTransactionQueues中。

mTransactionQueue数量的变化过程可以在perfetto中直观的看到,它在哪个阶段增加的哪个阶段消耗的一目了然:

两个循环中都使用到了transactionIsReadyToBeApplied()方法来辅助判断是否能被apply,该函数的主体功能就是:通过检查期望显示的时间戳、vsync时间戳是否与帧率同步、acquireFence是否已signaled以及buffer的状态来判断当前事务是否已经准备好。

最后一个for循环则是对上面收集到的transactions开始进行apply。

applyTransactionState()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

void SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo,
                                           const Vector<ComposerState>& states,
                                           const Vector<DisplayState>& displays, uint32_t flags,
                                           const InputWindowCommands& inputWindowCommands,
                                           const int64_t desiredPresentTime, bool isAutoTimestamp,
                                           const client_cache_t& uncacheBuffer,
                                           const int64_t postTime, uint32_t permissions,
                                           bool hasListenerCallbacks,
                                           const std::vector<ListenerCallbacks>& listenerCallbacks,
                                           int originPid, int originUid, uint64_t transactionId) {
    uint32_t transactionFlags = 0;
    for (const DisplayState& display : displays) {
        transactionFlags |= setDisplayStateLocked(display);
    }

    // start and end registration for listeners w/ no surface so they can get their callback.  Note
    // that listeners with SurfaceControls will start registration during setClientStateLocked
    // below.
    for (const auto& listener : listenerCallbacks) {
        mTransactionCallbackInvoker.startRegistration(listener);
        mTransactionCallbackInvoker.endRegistration(listener);
    }

    std::unordered_set<ListenerCallbacks, ListenerCallbacksHash> listenerCallbacksWithSurfaces;
    uint32_t clientStateFlags = 0;
    for (const ComposerState& state : states) {
        clientStateFlags |=
                setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, isAutoTimestamp,
                                     postTime, permissions, listenerCallbacksWithSurfaces);
        if ((flags & eAnimation) && state.state.surface) {
            if (const auto layer = fromHandle(state.state.surface).promote(); layer) {
                mScheduler->recordLayerHistory(layer.get(),
                                               isAutoTimestamp ? 0 : desiredPresentTime,
                                               LayerHistory::LayerUpdateType::AnimationTX);
            }
        }
    }

    for (const auto& listenerCallback : listenerCallbacksWithSurfaces) {
        mTransactionCallbackInvoker.endRegistration(listenerCallback);
    }

    // If the state doesn't require a traversal and there are callbacks, send them now
    if (!(clientStateFlags & eTraversalNeeded) && hasListenerCallbacks) {
        mTransactionCallbackInvoker.sendCallbacks();
    }
    transactionFlags |= clientStateFlags;

    if (permissions & Permission::ACCESS_SURFACE_FLINGER) {
        transactionFlags |= addInputWindowCommands(inputWindowCommands);
    } else if (!inputWindowCommands.empty()) {
        ALOGE("Only privileged callers are allowed to send input commands.");
    }

    if (uncacheBuffer.isValid()) {
        ClientCache::getInstance().erase(uncacheBuffer);
    }

    // If a synchronous transaction is explicitly requested without any changes, force a transaction
    // anyway. This can be used as a flush mechanism for previous async transactions.
    // Empty animation transaction can be used to simulate back-pressure, so also force a
    // transaction for empty animation transactions.
    if (transactionFlags == 0 &&
            ((flags & eSynchronous) || (flags & eAnimation))) {
        transactionFlags = eTransactionNeeded;
    }

    if (transactionFlags) {
        if (mInterceptor->isEnabled()) {
            mInterceptor->saveTransaction(states, mCurrentState.displays, displays, flags,
                                          originPid, originUid, transactionId);
        }

        // We are on the main thread, we are about to preform a traversal. Clear the traversal bit
        // so we don't have to wake up again next frame to preform an unnecessary traversal.
        if (transactionFlags & eTraversalNeeded) {
            transactionFlags = transactionFlags & (~eTraversalNeeded);
            mForceTraversal = true;
        }
        if (transactionFlags) {
            setTransactionFlags(transactionFlags);
        }

        if (flags & eAnimation) {
            mAnimTransactionPending = true;
        }
    }
}

第一个for循环暂时不用关注,因为一般情况下transaction不会去设置DisplayState。

第二个for循环也不用看,传进来为空。实际的ListenerCallbacks保存在ComposerState的state.listeners中。

第三个for循环才是重点内容:setClientStateLocked()

setClientStateLocked()

这个函数很长很长,这里不贴代码了。主要做了如下的工作:

  • 提取并设置回调机制的必要步骤。
  • 获取handle对应的layer,根据client设置的layer_state_t信息去填充layer的mDrawingState变量,关于layerstack的信息保存在surfaceflinger的mCurrentState中。
  • 根据listener构建CallbackHandle并将它们保存到mDrawingState.callbackHandles以及TransactionCallbackInvoker的mPendingTransactions中。

剩下来的内容都是围绕transactionFlags的标志来决定是否在主线程再发起setTransactionFlags()。

到此,flushTransactionQueues()执行完毕。

handleTransaction()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) {
    ATRACE_CALL();

    // here we keep a copy of the drawing state (that is the state that's
    // going to be overwritten by handleTransactionLocked()) outside of
    // mStateLock so that the side-effects of the State assignment
    // don't happen with mStateLock held (which can cause deadlocks).
    State drawingState(mDrawingState);

    Mutex::Autolock _l(mStateLock);
    mDebugInTransaction = systemTime();

    // Here we're guaranteed that some transaction flags are set
    // so we can call handleTransactionLocked() unconditionally.
    // We call getTransactionFlags(), which will also clear the flags,
    // with mStateLock held to guarantee that mCurrentState won't change
    // until the transaction is committed.

    modulateVsync(&VsyncModulator::onTransactionCommit);
    transactionFlags = getTransactionFlags(eTransactionMask);
    handleTransactionLocked(transactionFlags);

    mDebugInTransaction = 0;
    // here the transaction has been committed
}

这里会先做一个动作,把mTransactionFlags给清零。然后再进行handleTransactionLocked():

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags) {
    //省略

    commitTransaction();
}

这里主要看下commitTransaction()这个函数:

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::commitTransaction() {
    ATRACE_CALL();
    commitTransactionLocked();
    signalSynchronousTransactions(CountDownLatch::eSyncTransaction);
    mAnimTransactionPending = false;
}

void SurfaceFlinger::commitTransactionLocked() {
    if (!mLayersPendingRemoval.isEmpty()) {
        // Notify removed layers now that they can't be drawn from
        for (const auto& l : mLayersPendingRemoval) {
            recordBufferingStats(l->getName(), l->getOccupancyHistory(true));

            // Ensure any buffers set to display on any children are released.
            if (l->isRemovedFromCurrentState()) {
                l->latchAndReleaseBuffer();
            }

            // If the layer has been removed and has no parent, then it will not be reachable
            // when traversing layers on screen. Add the layer to the offscreenLayers set to
            // ensure we can copy its current to drawing state.
            if (!l->getParent()) {
                mOffscreenLayers.emplace(l.get());
            }
        }
        mLayersPendingRemoval.clear();
    }

    // If this transaction is part of a window animation then the next frame
    // we composite should be considered an animation as well.
    mAnimCompositionPending = mAnimTransactionPending;

    mDrawingState = mCurrentState;
    // clear the "changed" flags in current state
    mCurrentState.colorMatrixChanged = false;

    if (mVisibleRegionsDirty) {
        for (const auto& rootLayer : mDrawingState.layersSortedByZ) {
            rootLayer->commitChildList();
        }
    }

    commitOffscreenLayers();
    if (mNumClones > 0) {
        mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); });
    }
}

这段代码的核心内容是更新绘制状态mCurrentState->mDrawingState,并根据脏区域标记的情况来遍历所有图层提交子图层列表。其他内容则是对已被移除图层的一些处理工作,且将必要的图层加入offscreen layers,然后遍历并处理所有的offscreen layers。

code走到这里,client端传递过来的Transaction的内容在server端已经基本被处理完了,后面的内容则是surfaceflinger根据各个layer的情况去考虑具体的合成及送显情形了。

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

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

相关文章

SQL手工注入漏洞测试(MongoDB数据库)

此次靶场地址为&#xff1a;墨者学院 ⼀. 如下给出的源码...可以看到数据库查询的语句如下..构造回显测试... new_list.php?id1});return ({title:1,content:2 ⼆.成功显示“ 1” 和“ 2” 。可以在此来显示想要查询的数据。接下来开始尝试构造payload查询 当前数据库。通过…

Python基础知识学习总结(七)

文章目录 一. 函数1. 定义函数2. 语法及实例3. 函数调用4. 参数传递5. 可更改与不可更改对象6. 传可变对象实例 二. 文件I/O1. 定义2. File对象的属性3. open 函数4. write()方法5. read()方法6. 文件定位 一. 函数 函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实…

【微服务】SpringCloud Alibaba 10-14章

10 SpringCloud Alibaba入门简介 10.1 是什么 诞生 2018.10.31&#xff0c;Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器&#xff0c;并在 Maven 中央库发布了第一个版本。 Spring Cloud Alibaba 介绍 10.2 能干嘛 https://github.com/alibaba/spring-cloud-al…

芯片后端之 PT 使用 report_timing 产生报告 之 -nets 选项

今天&#xff0c;我们再学习一点点 后仿真相关技能。 那就是&#xff0c;了解 report_timing 中的 -nets 选项 。 如果我们仅仅使用如下命令&#xff0c;执行后会发现&#xff1a; pt_shell> report_timing -from start_point -to end_point -delay_type max report_ti…

JavaScript(31)——内置构造函数

构造函数 构造函数是一种特殊的函数&#xff0c;主要用于快速初始化对象 用大写字母开头只能由new操作符来执行 function Stu(name, age) {this.name namethis.age age}const xiaom new Stu(小明, 18)const xiaoh new Stu(小红, 19)console.log(xiaom);console.log(xiaoh…

catkin_make 编译报错CMake Error at /opt/ros/noetic/share/catkin/cmake/的最全解决办法,包治百病

检索&#xff08;解决安装了Anaconda后catkin_make不能用了&#xff0c;CMake Error at /opt/ros/noetic/share/catkin/cmake/catkinConfig.cmake:83 (find_package):Could not find a package configuration file provided by "serial" with any&#xff0c;Invokin…

Stable Diffusion AI绘画工具的安装与配置(MAC用户)

AI绘画的热潮席卷了整个创意行业&#xff0c;Stable Diffusion作为其中的翘楚&#xff0c;让艺术创作变得前所未有的简单。然而&#xff0c;对于使用Mac电脑用户来说&#xff0c;安装和配置Stable Diffusion可能显得有些棘手。别担心&#xff0c;这份详细的教程将手把手教你如何…

ARM——驱动——Linux启动流程和Linux启动

一、flash存储器 lash存储器&#xff0c;全称为Flash EEPROM Memory&#xff0c;又名闪存&#xff0c;是一种长寿命的非易失性存储器。它能够在断电情况下保持所存储的数据信息&#xff0c;因此非常适合用于存储需要持久保存的数据。Flash存储器的数据删除不是以单个的字节为单…

Cetos7安装详细流程

CentOS 7是一个流行的Linux发行版&#xff0c;广泛用于服务器和桌面环境。以下是在物理机或虚拟机上安装CentOS 7的详细步骤&#xff1a; 准备工作 下载CentOS 7 ISO&#xff1a;访问CentOS官方网站下载CentOS 7的ISO镜像文件。 创建启动介质&#xff1a;使用ISO镜像文件创建…

how to connect the VRTE to Internet

Are all gen_swp_fb python dependecies installed? In the build log it is present a fail of gen_swp_fb tool? Steps: Configure the VM according to proxy/no-proxy$ cd ../vrte/project/AraUCM_SwUpdate/gen_swp_fb/$ python3 -m pip install -r requirements.txtR…

普元EOS-利用热更新(热启动)提高开发效率

1 简介 在程序开发的时候&#xff0c;需要频繁的重启项目&#xff0c;随着项目复杂度增加&#xff0c;项目启动时间会越来越长。那么每次修改一点内容就重启项目&#xff0c;会极大影响开发效率。 EOS提供了热更新的手段&#xff0c;代码修改后无须重启&#xff0c;自动生效的…

扩展语言,扩展思维:LLM 词汇量缩放

“我的语言的极限意味着我的世界的极限。”——维特根斯坦 大型语言模型 (LLM)已实现可靠的性能。这一切都归功于 Transformer 及其以自监督方式从大量文本中学习的能力。显然&#xff0c;这种简单的方法允许模型学习越来越复杂的文本表示&#xff0c;而无需人工解释。这使得收…

考研资讯平台

TOC springboot0767考研资讯平台 绪论 1.1课题研究背景与意义 随着现代网络技术的快速发展&#xff0c;互联网的应用对学生的生活和工作有着很大的影响&#xff0c;特别是在当今计算机的应用下的人更加需要这样的环境&#xff0c;所以我们根据这个要求来开发了本课题。该课…

集团数字化转型方案(十)

集团数字化转型方案将通过全面部署云计算、大数据分析、人工智能和物联网技术&#xff0c;构建一个全方位的数据驱动平台&#xff0c;实现从战略规划到运营管理的数字化升级&#xff0c;以优化业务流程、提高决策效率、增强客户体验和提升运营灵活性。该方案包括智能化的供应链…

Docker!!!

⼀、Docker 1、Docker介绍.pdf 1、Docker 是什么&#xff1f; Docker 是⼀个开源的应⽤容器引擎&#xff0c;可以实现虚拟化&#xff0c;完全采⽤“沙盒”机制&#xff0c;容器之间不会存在任何接⼝。Docker 通过 Linux Container&#xff08;容器&#xff09;技术将任意类型…

5步掌握Python Django+Vue二手房项目,实现房价预测与知识图谱系统

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

破晓科技与神话:三防平板与《黑神话:悟空》的创新交响

当全球游戏圈因《黑神话&#xff1a;悟空》的震撼预告而沸腾&#xff0c;一款代表中国游戏顶尖制作水平的作品&#xff0c;正以它独特的文化魅力与技术创新&#xff0c;向世界宣告着中国游戏产业的崛起。 点击添加图片描述&#xff08;最多60个字&#xff09;编辑 震撼视觉体验…

解题—求两数的最大公约数与最小公倍数 #辗转相除法

文章目录 前言 一、最大公约数 方法一&#xff1a;硬解 方法二&#xff1a;辗转相除法 1、图形理解&#xff1a; 2、公式理解&#xff1a; 二、最小公倍数 方法一&#xff1a;硬解 方法二&#xff1a;巧解 总结 前言 路漫漫其修远兮&#xff0c;吾将上下而求索。 一、…

spring security 入门基础,表单认证web页面跳转

一、导入所需依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.2</version></parent><!-- web 支持 --><dependency><groupId>…

机械学习—零基础学习日志(如何理解概率论5)

这里我们学习随机变量的独立性 我们这里也来一个习题。 《概率论与数理统计期末不挂科|考研零基础入门4小时完整版&#xff08;王志超&#xff09;》学习笔记 王志超老师 &#xff08;UP主&#xff09;