Android Framework(六)WMS-窗口显示流程——窗口内容绘制与显示

文章目录

  • 窗口显示流程
    • 明确目标
  • 窗户内容绘制与显示流程
    • 窗口Surface状态
    • 完整流程图
  • 应用端处理
    • finishDrawingWindow 的触发
  • system_service处理
    • WindowState状态 -- COMMIT_DRAW_PENDING
    • 本次layout 流程简述

窗口显示流程

在这里插入图片描述
目前窗口的显示到了最后一步。
在 addWindow 流程中,创建挂载了 WindowState
在 relayoutWindow 流程为这个窗口创建了 Surface 并且还计算好了这个窗口的大小和在屏幕上的位置,并把窗口的 Surface 状态设置为了 DRAW_PENDING 。
这一步执行完后,应用端就可以开始绘制 View 树了,绘制完成后,需要把内容显示到屏幕上,也就是现在这个 Surface ,这一步就是本篇分析的内容:finishDrawingWindow 流程。

明确目标

现在可以明确 finishDrawingWindow 流程目的只有1个:把窗口的内容显示到屏幕上。
这里可能会有几个疑问:

  • 1、在 relayoutWindow 流程不是已经创建好 Surface 了吗?这一步目的是“把窗口的内容显示到屏幕上”,应用端拿到 Surface 绘制完 UI 等 VSync 来的时候上帧不就屏幕上有画面了?

  • 2、难道应用端每一帧绘制完都需要走这个 finishDrawingWindow 流程吗?

回答问题之前,先看一个案例:屏幕旋转

在屏幕旋转的时候会应用窗口的改变,这一阶段手机屏幕的 UI 的很混乱的,为了提示用户体验,google 的做法是旋转之前截个屏,然后创建一个层级很高的图层显示这个截图。这样一来旋转期间,用户看到的就是截图的内容。

这里又有个疑问,既然是这样那为啥还有那么多旋转黑屏的问题?这是因为动画执行的时间是写在动画文件里的,并不是根据旋转逻辑执行完毕来的(我本以为是以这个为动画结束条件)。当然这不是当前重点。

所以现在看一下这个截图图层的代码逻辑:

# ScreenRotationAnimation// 截屏图层SurfaceControlprivate SurfaceControl mScreenshotLayer;ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {......// 拿到一个事务final SurfaceControl.Transaction t = mService.mTransactionFactory.get();try {       ......// 1. 创建截图的图层String name = "RotationLayer";mScreenshotLayer = displayContent.makeOverlay().setName(name).......setBLASTLayer() // 设置为“Buff”图层.build();......// 获取截图buffGraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(screenshotBuffer.getHardwareBuffer());......t.setBuffer(mScreenshotLayer, buffer);// 2. 设置截图buff给截图的图层t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());// 3. 执行显示 Surfacet.show(mScreenshotLayer);t.show(mBackColorSurface); } ............// 4. 事务提交t.apply();}

在上层控制一个 Surface 的显示分为4步:

  • 1、创建 SurfaceControl
  • 2、设置 buff 给SurfaceControl (显示内容)
  • 3、执行 SurfaceControl.Transaction::show 传递这个 Surface
  • 4、执行 SurfaceControl.Transaction::apply 通知 SurfaceFlinger
    在这里插入图片描述
    也就是说一个 Surface 最新显示到屏幕上是通过 SurfaceControl.Transaction 事务完成的,需要把显示 Surface 的要求加的事务中(show) 在执行事务的条件,这样 SurfaceFlinger 才会把显示对应的 Layer 。

其中 1,2 两步在 relayoutWindow 流程和应用端绘制 View 树就完成了,这个时候 Surface 下的 buff 是有内容的,但是还没有显示到屏幕上。所以要执行 finishDrawingWindow 流程来让 SurfaceFlinger 显示这个 Layer 。这也是第一个问题的答案。

第二个问题的答案是应用不需要每一帧绘制完都执行 finishDrawingWindow 流程,后面的应用上帧就是通过 Surface 内部的生产消费者模型完成。

窗户内容绘制与显示流程

在这里插入图片描述
如图整个流程可以分为以下几步:

  • 1、应用端绘制后(View绘制三部曲)触发 finishDrawingWindow 流程
  • 2、system_service 将窗口的 Surface 状态从原来是 DRAW_PENDING 更新到 COMMIT_DRAW_PENDING 表示准备提交
  • 3、然后触发一次 layout 这次 layout 的目的是将这个窗口的 Surface 显示到屏幕上
    • 3.1 状态设置到 READY_TO_SHOW 表示准备显示
    • 3.2 状态设置到 HAS_DRAWN 表示已经显示在屏幕上
    • 3.3 把这次 layout 对 Surface 的操作通过 SurfaceControl.Transaction 统一提交到 SurfaceFlinger
  • 4、SurfaceFlinger 显示窗口的 Layer

窗口Surface状态

窗口Surface状态定义在 WindowStateAnimator.java 下面,结合源码的注释和实际场景简单解释一下各个状态:

# WindowStateAnimator/** This is set when there is no Surface */// 没有 Surface的时候,说明没有创建或者窗口销毁static final int NO_SURFACE = 0;/** This is set after the Surface has been created but before the window has been drawn. During* this time the surface is hidden. */// Surface 刚刚创建但是还没绘制的状态。 也就是 relayoutWindow 流程时设置的static final int DRAW_PENDING = 1;/** This is set after the window has finished drawing for the first time but before its surface* is shown.  The surface will be displayed when the next layout is run. */// 窗口第一次完成绘制之后的状态,将在下一次 layout 的时候执行。// 是等待提交到SF的状态static final int COMMIT_DRAW_PENDING = 2;/** This is set during the time after the window's drawing has been committed, and before its* surface is actually shown.  It is used to delay showing the surface until all windows in a* token are ready to be shown. */// 已经提交到SF, 准备显示到屏幕上static final int READY_TO_SHOW = 3;/** Set when the window has been shown in the screen the first time. */// 窗口已经显示static final int HAS_DRAWN = 4;

Surface状态的状态切换流程如下:
在这里插入图片描述

  • 1、在 relayoutWindow 流程创建 Surface 后在 createSurfaceLocked 方法将状态设置为 DRAW_PENDING

  • 2、应用绘制完成会后触发 finishDrawingWindow 方法,这个方法分为以下几步:

    • 2.1 流程开始就通过 finishDrawingLocked 方法设置状态为 COMMIT_DRAW_PENDING

    • 2.2 执行 requestTraversal 触发 layout 流程,这里可能对多次执行。相关的事情都在内部的 applySurfaceChangesTransaction 方法中处理

      • 2.2.1 在 commitFinishDrawingLocked 方法把窗口状态设置为 READY_TO_SHOW
      • 2.2.2 在 performShowLocked 方法把窗口状态设置为 HAS_DRAWN
      • 2.2.3 执行 prepareSurfaces 方法,最终构建窗口 Surface 显示的事务
  • 3、layout 流程 WindowManagerService::closeSurfaceTransaction 方法里会真正将事务提交到 SurfaceFlinger 处理

所以现在更明确的当前流程的主线任务:
1、找到设置 Surface 状态为 COMMIT_DRAW_PENDING、 READY_TO_SHOW 和 HAS_DRAWN 的地方。
2、找到执行 SurfaceControl.Transaction::show 和 SurfaceControl.Transaction::apply 执行的地方就完成了。

完整流程图

本篇的主要逻辑和上篇一样,也是在一次 layout 里,layout 几户覆盖了所有的窗口逻辑,非常复杂,这里只贴出关于窗口显示逻辑流程图:
在这里插入图片描述

应用端处理

既然是绘制完成后的处理,触发的地方还是应用端本身,只有应用端绘制完成了才会触发逻辑。

再看一下 ViewRootImpl::setView 的调用链:

ViewRootImpl::setViewViewRootImpl::requestLayoutViewRootImpl::scheduleTraversals             ViewRootImpl.TraversalRunnable::run              -- Vsync相关--scheduleTraversalsViewRootImpl::doTraversalViewRootImpl::performTraversals ViewRootImpl::relayoutWindowSession::relayout                -- 第二步:relayoutWindowViewRootImpl::updateBlastSurfaceIfNeededSurface::transferFrom        -- 应用端Surface赋值ViewRootImpl::performMeasure         -- View绘制三部曲 --MeasureViewRootImpl::performLayout          -- View绘制三部曲 --Layout  ViewRootImpl::createSyncIfNeededSurfaceSyncGroup::init ViewRootImpl::reportDrawFinished Session::finishDrawing   -- 第三步:finishDrawingWindowViewRootImpl::performDraw            -- View绘制三部曲 --Draw    SurfaceSyncGroup::markSyncReady      -- 触发绘制完成回调Session.addToDisplayAsUser                                -- 第一步:addWindow

在这里插入图片描述
前面分析【relayoutWindow流程】的时候已经分析过 ViewRootImpl::performTraversals 方法了,不过当前重点不一样,所以还需要再看一遍这个方法(增加了一些当前流程相关的代码)

  • 1、后续需要介绍软绘硬绘的流程,所以可以看到硬绘的初始化逻辑也在这个方法
  • 2、relayoutWindow 相关
  • 3、经过第二步 relayoutWindow 后 View 就可以绘制了
  • 4、绘制完成后就要通知 SurfaceFlinger 进行合成了,也就是本篇分析的 finishDrawing 流程

当前分析 finishDrawing 流程,首先可以看到 relayoutWindow 方法执行后,会触发3个View绘制的方法,也就是常说的 View 绘制三部曲:measure、layout、draw。

但是这里有个奇怪的地方: “4.1 createSyncIfNeeded” 方法是触发 finishDrawingWindow 的,但是这个方法在 “3.3 performDraw”的上面。

这是因为代码的顺序不代表真正的执行顺序,这里的“4.1 createSyncIfNeeded”只是设置了“回调”,等时机到了就会触发执行,而这个时机就是 View 绘制完成后,在 “4.2 markSyncReady 触发”

这一部分的逻辑有点绕,不过目前分析的是主流程,所以这块逻辑以上的描述当黑盒理解这段的调用: View 绘制结束后就会在 4.2 出触发 4.1 内部的执行,进入触发 finishDrawingWindow 流程即可。
这部分的代码 U 做了重构,后面再单独写一篇详细解释直接的调用逻辑。

finishDrawingWindow 的触发

在 ViewRootImpl::performTraversals 方法最后会执行 SurfaceSyncGroup::markSyncReady 方法,最终会触发 ViewRootImpl::createSyncIfNeeded 方法下的 ViewRootImpl::reportDrawFinished 来真正 finishDrawingWindow 流程。

# ViewRootImpl// 创建对象private SurfaceSyncGroup mActiveSurfaceSyncGroup;// 是否有同步的内容需要上报boolean mReportNextDraw;private void createSyncIfNeeded() {// 如果已经在本地进行同步或者没有需要同步的内容// mReportNextDraw 变量也是控制每一帧绘制完不都要执行 finishDrawingWindow 流程的原因if (isInWMSRequestedSync() || !mReportNextDraw) {return;}// 获取当前同步序列号final int seqId = mSyncSeqId;// 传入一个匿名类mWmsRequestSyncGroupState = WMS_SYNC_PENDING;mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> {// 合并传入的transaction到mSurfaceChangedTransaction中mWmsRequestSyncGroupState = WMS_SYNC_MERGED;// 重点* 报告绘制完成,传入之前获取的序列号reportDrawFinished(t, seqId);});if (DEBUG_BLAST) {// 打印日志Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());}// 将mSyncTarget添加到mSyncId对应的同步中mWmsRequestSyncGroup.add(this, null /* runnable */);}

这个方法的重点就是在应用绘制完成后触发 finishDrawingWindow 流程,也就是触发 ViewRootImpl::reportDrawFinished 方法。

# ViewRootImplprivate void reportDrawFinished(@Nullable Transaction t, int seqId) {// 日志和Trace相关if (DEBUG_BLAST) {Log.d(mTag, "reportDrawFinished");}if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId);}try {// 重点* finishDrawing流程mWindowSession.finishDrawing(mWindow, t, seqId);......} ............}# Session@Overridepublic void finishDrawing(IWindow window,@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishDrawing: " + mPackageName);}// 触发WMS 执行finishDrawingWindow 流程mService.finishDrawingWindow(this, window, postDrawTransaction, seqId);Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}

这个方法的重点就是在应用绘制完成后触发 finishDrawingWindow 流程,也就是触发 ViewRootImpl::reportDrawFinished 方法。

# ViewRootImplprivate void reportDrawFinished(@Nullable Transaction t, int seqId) {// 日志和Trace相关if (DEBUG_BLAST) {Log.d(mTag, "reportDrawFinished");}if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId);}try {// 重点* finishDrawing流程mWindowSession.finishDrawing(mWindow, t, seqId);......} ............}# Session@Overridepublic void finishDrawing(IWindow window,@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishDrawing: " + mPackageName);}// 触发WMS 执行finishDrawingWindow 流程mService.finishDrawingWindow(this, window, postDrawTransaction, seqId);Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}

唯一做的一件事就是跨进程触发 WindowManagerService::finishDrawingWindow 。 到这里应用端的事情就处理完了,后面的流程在 system_service 进程。

system_service处理

system_service 处理主要的调用链整理如下:

WindowManagerService::finishDrawingWindowWindowState::finishDrawingWindowStateAnimator::finishDrawingLocked    -- COMMIT_DRAW_PENDINGWindowPlacerLocked::requestTraversal           -- 触发layoutTraverser::runWindowSurfacePlacer::performSurfacePlacementWindowSurfacePlacer::performSurfacePlacementLoopRootWindowContainer::performSurfacePlacement  -- 开始layout逻辑RootWindowContainer::performSurfacePlacementNoTraceWindowManagerService::openSurfaceTransaction      -- 打开Surface事务RootWindowContainer::applySurfaceChangesTransaction  -- 处理Surface事务DisplayContent::applySurfaceChangesTransaction   -- 遍历每个屏幕DisplayContent::performLayout                          -- relayoutWinodw 流程DisplayContent::forAllWindows                          -- 每个窗口执行mApplySurfaceChangesTransactionWindowStateAnimator::commitFinishDrawingLocked     -- READY_TO_SHOWWindowState::performShowLocked                 -- HAS_DRAWNDisplayContent::prepareSurfaces                        -- Surface 处理WindowContainer::prepareSurfaces                   -- 遍历每个孩子WindowState::prepareSurfaces                   -- 忽略其他,只看窗口的实现WindowStateAnimator::prepareSurfaceLockedWindowSurfaceController::showRobustlyWindowSurfaceController::setShownSurfaceControl.Transaction::show    -- Surface显示WindowManagerService::closeSurfaceTransaction    -- 处理关闭Surface事务SurfaceControl::closeTransactionGlobalTransactionWrapper::applyGlobalTransactionGlobalTransactionWrapper::nativeApplyTransaction    -- 触发native

在这里插入图片描述

# WindowManagerServicefinal WindowSurfacePlacer mWindowPlacerLocked;void finishDrawingWindow(Session session, IWindow client,@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {if (postDrawTransaction != null) {postDrawTransaction.sanitize(Binder.getCallingPid(), Binder.getCallingUid());}final long origId = Binder.clearCallingIdentity();try {synchronized (mGlobalLock) {// 获取到对应的WindowStateWindowState win = windowForClientLocked(session, client, false);// T版本这里是个 proto日志Slog.w(TAG, "finishDrawingWindow: "+win+" mDrawState="+(win != null ? win.mWinAnimator.drawStateToString() : "null"));// 重点* 1. 执行WindowState::finishDrawingif (win != null && win.finishDrawing(postDrawTransaction, seqId)) {if (win.hasWallpaper()) {win.getDisplayContent().pendingLayoutChanges |=WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;}// 将当前WindowState.mLayoutNeeded置为truewin.setDisplayLayoutNeeded();// 重点* 2. 请求进行布局刷新mWindowPlacerLocked.requestTraversal();}}} finally {Binder.restoreCallingIdentity(origId);}}

system_service 进程第一个处理的方法就是 WindowManagerService::finishDrawingWindow 这个方法也就做了2件事:

  • 1、WindowState::finishDrawing 将 Surface 状态设置为 COMMIT_DRAW_PENDING
  • 2、WindowSurfacePlacer::requestTraversal 框架层的 layout ,将状态设置为 READY_TO_SHOW ,HAS_DRAWN ,然后通知到 SurfaceFlinger

这里有上述的2个流程需要分析,首先会执行 WindowState::finishDrawing ,将WindowState状态设置为 COMMIT_DRAW_PENDING ,表示应用端已经绘制完成了,可以提交给SF了。
第一步操作完之后,就会执行 WindowSurfacePlacer::requestTraversal ,这个方法是执行一次 layout 逻辑。

在前面看窗口状态 COMMIT_DRAW_PENDING 定义的时候,google 注释提过: “会下一次 layout 的时候显示到屏幕上”,指的就是在这里触发的 layout。

在第二步 layout 的时候会遍历每个窗口,目前只关心当前分析的场景的这个窗口,在这次 layout 会做3件事:

  • 1、将窗口状态设置为 READY_TO_SHOW
  • 2、将窗口状态设置为 HAS_DRAWN
  • 3、通过 SurfaceControl.Transaction 通知 SurfaceFlinger 做显示合成

下面开始在代码中梳理流程。

WindowState状态 – COMMIT_DRAW_PENDING

# WindowStatefinal WindowStateAnimator mWinAnimator;boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction, int syncSeqId) {......// 主流程final boolean layoutNeeded =mWinAnimator.finishDrawingLocked(postDrawTransaction, mClientWasDrawingForSync);mClientWasDrawingForSync = false;// We always want to force a traversal after a finish draw for blast sync.return !skipLayout && (hasSyncHandlers || layoutNeeded);}

主要是执行了 WindowStateAnimator::finishDrawingLocked ,内部会将 WindowState 的状态设置为 COMMIT_DRAW_PENDING ,这个是非常重要的一步。

# WindowStateAnimatorboolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction,boolean forceApplyNow) {......// 只有当前状态是DRAW_PENDING的时候才可以走进逻辑if (mDrawState == DRAW_PENDING) {ProtoLog.v(WM_DEBUG_DRAW,"finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,mSurfaceController);if (startingWindow) {// 如果是StartingWindow还有专门的logProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);}mDrawState = COMMIT_DRAW_PENDING;// 表示需要 layoutlayoutNeeded = true;}......}

这样第一步就执行完了,流程很简单,只是设置窗口状态为 COMMIT_DRAW_PENDING 。

本次layout 流程简述

上一小节只是改了状态,下一个状态是 READY_TO_SHOW ,前面看到google对它有一个注释:The surface will be displayed when the next layout is run.
也就是说在下一次 layout 会触发 Surface 的显示,所以关键流程还是在 “next layout”,
那什么是 “next layout” ?
我们知道屏幕上有任何风吹操作都会触发一次 layout 流程,主要就是执行 WindowSurfacePlacer::performSurfacePlacement 这就是 一次 layout 。

WindowPlacerLocked::requestTraversal 触发的 layout 流程就是之前 relayoutWindow 流程看到的 WindowSurfacePlacer::performSurfacePlacement 。这个流程触发的地方非常多,只是当前 finishDrawingWindow 会主动触发一次罢了。对于这种高频率触发的方法,需要留意一下,初学者知道每个主流程会走什么逻辑就好,慢慢的随着知识体系的构建,再看这个流程其实就没那么复杂了。

WindowSurfacePlacer::performSurfacePlacement 的逻辑会遍历屏幕上每一个窗口,然后让其根据最新情况做对应的处理,比如 relayoutWinodw 流程的时候就会遍历到窗口做
执行 computeFrames 计算窗口大小。

当前分析的场景自然也会遍历窗口,触发这次 layout 的目的就是让当前这个窗口的 Surface 提交到 SurfaceFlinger 。

这个流程之前看过了,所以直接从 RootWindowContainer::performSurfacePlacement 方法开始

# RootWindowContainer// 这个方法加上了tracevoid performSurfacePlacement() {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");try {performSurfacePlacementNoTrace();} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}}// 主要干活的还是这个void performSurfacePlacementNoTrace() {......// TraceTrace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");// 开启Surface事务mWmService.openSurfaceTransaction();try {// 重点* 1. 处理Surface事务applySurfaceChangesTransaction();} catch (RuntimeException e) {Slog.wtf(TAG, "Unhandled exception in Window Manager", e);} finally {// 关闭Surface事务mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}......// 重点* 2. 处理App事务checkAppTransitionReady(surfacePlacer);......}

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

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

相关文章

C语言中数据类型

一、C 语言中数据类型 基本数据类型: 整型(int):用于存储整数,如:1、2、3等。字符型(char):用于存储单个字符,如:‘a’、‘b’、c’等。浮点型&a…

中秋献礼!2024年中科院一区极光优化算法+分解对比!VMD-PLO-Transformer-LSTM多变量时间序列光伏功率预测

中秋献礼!2024年中科院一区极光优化算法分解对比!VMD-PLO-Transformer-LSTM多变量时间序列光伏功率预测 目录 中秋献礼!2024年中科院一区极光优化算法分解对比!VMD-PLO-Transformer-LSTM多变量时间序列光伏功率预测效果一览基本介…

一种多策略改进小龙虾智能优化算法MSCOA 改进策略:种群混沌映射初始化+透镜成像反向学习+黄金正弦变异策略

一种多策略改进小龙虾智能优化算法MSCOA 改进策略:种群初始化精英反向透镜成像反向学习黄金正弦变异策略 文章目录 一、小龙虾COA基本原理二、改进策略2.1种群初始化 映射2.2 透镜成像反向学习2.3 黄金正弦变异策略 三、实验结果四、核心代码五、代码获取六、总结 一…

每日一个数据结构-跳表

文章目录 什么是跳表?示意图跳表的基本原理跳表的操作跳表与其他数据结构的比较 跳表构造过程 什么是跳表? 跳表(Skip List)是一种随机化的数据结构,它通过在有序链表上增加多级索引来实现快速查找、插入和删除操作。…

react hooks--useState

概述 useState 可以使函数组件像类组件一样拥有 state,也就说明函数组件可以通过 useState 改变 UI 视图。那么 useState 到底应该如何使用,底层又是怎么运作的呢,首先一起看一下 useState 。 问题:Hook 是什么? 一个 Hook 就是…

TensorRT-LLM——优化大型语言模型推理以实现最大性能的综合指南

引言 随着对大型语言模型 (LLM) 的需求不断增长,确保快速、高效和可扩展的推理变得比以往任何时候都更加重要。NVIDIA 的 TensorRT-LLM 通过提供一套专为 LLM 推理设计的强大工具和优化,TensorRT-LLM 可以应对这一挑战。TensorRT-LLM 提供了一系列令人印…

Double Write

优质博文:IT-BLOG-CN 一、存在的问题 为什么需要Double Write: InnoDB的PageSize是16kb,其数据校验也是针对这16KB来计算的,将数据写入磁盘是以Page为单位的进行操作的。而计算机硬件和操作系统,写文件是以4KB作为基…

Python基础语法(1)上

常量和表达式 我们可以把 Python 当成一个计算器,来进行一些算术运算。 print(1 2 - 3) print(1 2 * 3) print(1 2 / 3) 这里我们可能会有疑问,为什么不是1.6666666666666667呢? 其实在编程中,一般没有“四舍五入”这样的规则…

基于Python DoIPClient库的DoIP上位机开发手顺

代码 address, announcement DoIPClient.await_vehicle_announcement()logical_address announcement.logical_addressip, port addressprint(ip, port, logical_address) 效果 代码 address, announcement DoIPClient.get_entity(ecu_ip_addresssIp, protocol_version3…

二叉树OJ题——相同的树

文章目录 一、题目链接二、解题思路三、解题代码 一、题目链接 相同的树 二、解题思路 时间复杂度:O(min(n,m)) 三、解题代码

解决IDEA每次创建新项目时都要指定Maven仓库和Maven配置文件的问题

文章目录 0. 前言1. 打开新项目的设置2. 搜索 Maven 相关的配置3. 更改Maven主路径、配置文件、本地仓库4. 更改新项目的Maven配置后没生效 0. 前言 在 IDEA 中每次创建新项目时,使用的都是默认的 Maven 仓库和默认的配置文件,需要我们手动修改&#xf…

利用AI驱动智能BI数据可视化-深度评测Amazon Quicksight(三)

简介 随着生成式人工智能的兴起,传统的 BI 报表功能已经无法满足用户对于自动化和智能化的需求,今天我们将介绍亚马逊云科技平台上的AI驱动数据可视化神器 – Quicksight,利用生成式AI的能力来加速业务决策,从而提高业务生产力。…

SpringSecurity原理解析(八):CSRF防御解析

一、CsrfFilter CsrfFilter 主要功能是用来防止csrf攻击 一、什么是CSRF攻击 跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF&#xff0c…

【IP协议】IP协议报头结构

文章目录 IP 协议报头结构4位版本4位首部长度8位服务类型16位总长度16位标识、3位标志、13位片偏移8位生存时间8位协议16位首部校验和32源 IP 地址、32位目的 IP 地址 IP 协议报头结构 4位版本 实际上只有两个取值 4 > IPv4(主流)6 > IPv6 IPv2&…

浅谈人工智能之基于ollama本地大模型结合本地知识库搭建智能客服

浅谈人工智能之基于ollama本地大模型结合本地知识库搭建智能客服 摘要 随着人工智能技术的飞速发展,基于大型语言模型(LLMs)的智能客服系统逐渐成为提升企业服务质量和效率的关键工具。然而,对于注重数据隐私和安全的企业而言,使用云服务可能会引发数据泄露的风险。因此…

【C++题解】1996. 每个小组的最大年龄

欢迎关注本专栏《C从零基础到信奥赛入门级(CSP-J)》 问题:1996. 每个小组的最大年龄 类型:二维数组 题目描述: 同学们在操场上排成了一个 n 行 m 列的队形,每行的同学属于一个小组,请问每个小…

2022高教社杯全国大学生数学建模竞赛C题 问题一(1) Python代码演示

目录 问题 11.1 对这些玻璃文物的表面风化与其玻璃类型、纹饰和颜色的关系进行分析数据探索 -- 单个分类变量的绘图树形图条形图扇形图雷达图Cramer’s V 相关分析统计检验列联表分析卡方检验Fisher检验绘图堆积条形图分组条形图分类模型Logistic回归随机森林import matplotlib…

SPI学习笔记

SPI SPI是一种同步串行通信接口规范,它允许一个主设备与一个或多个从设备进行全双工通信。SPI用于短距离通信,主要应用于嵌入式系统。 SPI通信过程 1.初始化:SPI主机首先将SS或CS线拉低,以选择特定的从设备并开始通信。 2.数据…

linux文件系统权限详解

注:目录的执行权限代表是否可以进入。 一、文件权限控制对文件的访问: 可以针对文件所属用户、所属组和其他用户可以设置不同的权限 权限具有优先级。user权限覆盖group权限,后者覆盖other权限。 有三种权限类别:读取、写入和执行 读权限:对文件:可读取文件…

集群聊天服务器项目【C++】(五)网络模块和业务模块

经过前面介绍相关的库和工具,比如Json、CMake、muduo等,我们可以开始编写本项目的代码了。 1.项目目录创建 一般一个项目由以下结构组成: bin文件夹存放:可执行程序build文件夹存放:编译过程中的临时文件include文…