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

news2024/9/20 18:43:57

文章目录

  • 窗口显示流程
    • 明确目标
  • 窗户内容绘制与显示流程
    • 窗口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
    // 截屏图层SurfaceControl
    private 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();
            ......
            // 获取截图buff
            GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
                    screenshotBuffer.getHardwareBuffer());
            ......
            t.setBuffer(mScreenshotLayer, buffer);// 2. 设置截图buff给截图的图层
            t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
            // 3. 执行显示 Surface
            t.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::setView
   ViewRootImpl::requestLayout
      ViewRootImpl::scheduleTraversals             
            ViewRootImpl.TraversalRunnable::run              -- Vsync相关--scheduleTraversals
                ViewRootImpl::doTraversal
                    ViewRootImpl::performTraversals 
                        ViewRootImpl::relayoutWindow
                            Session::relayout                -- 第二步:relayoutWindow
                            ViewRootImpl::updateBlastSurfaceIfNeeded
                                Surface::transferFrom        -- 应用端Surface赋值
                        ViewRootImpl::performMeasure         -- View绘制三部曲 --Measure
                        ViewRootImpl::performLayout          -- View绘制三部曲 --Layout  
                        ViewRootImpl::createSyncIfNeeded
                            SurfaceSyncGroup::init 
                                ViewRootImpl::reportDrawFinished 
                                    Session::finishDrawing   -- 第三步:finishDrawingWindow
                        ViewRootImpl::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 方法。

# ViewRootImpl

    private 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
    @Override
    public 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 方法。

# ViewRootImpl

    private 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
    @Override
    public 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::finishDrawingWindow
    WindowState::finishDrawing
        WindowStateAnimator::finishDrawingLocked    -- COMMIT_DRAW_PENDING
    WindowPlacerLocked::requestTraversal           -- 触发layout
        Traverser::run
            WindowSurfacePlacer::performSurfacePlacement
                WindowSurfacePlacer::performSurfacePlacementLoop
                    RootWindowContainer::performSurfacePlacement  -- 开始layout逻辑
                        RootWindowContainer::performSurfacePlacementNoTrace
                            WindowManagerService::openSurfaceTransaction      -- 打开Surface事务
                            RootWindowContainer::applySurfaceChangesTransaction  -- 处理Surface事务
                                DisplayContent::applySurfaceChangesTransaction   -- 遍历每个屏幕
                                    DisplayContent::performLayout                          -- relayoutWinodw 流程
                                    DisplayContent::forAllWindows                          -- 每个窗口执行mApplySurfaceChangesTransaction
                                        WindowStateAnimator::commitFinishDrawingLocked     -- READY_TO_SHOW
                                            WindowState::performShowLocked                 -- HAS_DRAWN
                                    DisplayContent::prepareSurfaces                        -- Surface 处理
                                        WindowContainer::prepareSurfaces                   -- 遍历每个孩子
                                            WindowState::prepareSurfaces                   -- 忽略其他,只看窗口的实现
                                            WindowStateAnimator::prepareSurfaceLocked
                                                WindowSurfaceController::showRobustly
                                                    WindowSurfaceController::setShown
                                                        SurfaceControl.Transaction::show    -- Surface显示
                            WindowManagerService::closeSurfaceTransaction    -- 处理关闭Surface事务
                                SurfaceControl::closeTransaction
                                    GlobalTransactionWrapper::applyGlobalTransaction
                                        GlobalTransactionWrapper::nativeApplyTransaction    -- 触发native

在这里插入图片描述

# WindowManagerService
    final 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) {
                // 获取到对应的WindowState
                WindowState win = windowForClientLocked(session, client, false);
                // T版本这里是个 proto日志
                Slog.w(TAG, "finishDrawingWindow: "+win+" mDrawState="
                         +(win != null ? win.mWinAnimator.drawStateToString() : "null"));
                // 重点* 1. 执行WindowState::finishDrawing
                if (win != null && win.finishDrawing(postDrawTransaction, seqId)) {
                    if (win.hasWallpaper()) {
                        win.getDisplayContent().pendingLayoutChanges |=
                                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
                    }
                    // 将当前WindowState.mLayoutNeeded置为true
                    win.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

# WindowState
    final 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 ,这个是非常重要的一步。

# WindowStateAnimator

    boolean 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还有专门的log
                ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
            }
            mDrawState = COMMIT_DRAW_PENDING;
            // 表示需要 layout
            layoutNeeded = 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

    // 这个方法加上了trace
    void performSurfacePlacement() {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");
        try {
            performSurfacePlacementNoTrace();
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }
    // 主要干活的还是这个
    void performSurfacePlacementNoTrace() {
        ......
        // Trace
        Trace.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.coloradmin.cn/o/2141175.html

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

相关文章

【ESP32】Arduino开发 | 中断矩阵+按键输入中断例程

对于中断矩阵的详细介绍会放在ESP-IDF开发文章中,跳转栏目目录可以找到对应文章。 1. API 1.1 绑定GPIO中断 attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode); pin:管脚号;handler:中断处理函数;mode…

【OJ刷题】双指针问题5

这里是阿川的博客,祝您变得更强 ✨ 个人主页:在线OJ的阿川 💖文章专栏:OJ刷题入门到进阶 🌏代码仓库: 写在开头 现在您看到的是我的结论或想法,但在这背后凝结了大量的思考、经验和讨论 目录 1…

【Scala入门学习】基本数据类型和变量声明

1. 基本数据类型 scala 的基本类型有 9种: Byte、Char、Short、Int、Long、Float、Double、Boolean、Unit Scala中没有基本数据类型的概念,所有的类型都是对象。 AnyVal:代表所有基本类型。 AnyRef:代表所以引用类型&#xff…

基于微信小程序的科创微应用平台设计与实现+ssm(lw+演示+源码+运行)

基于微信小程序的科创微应用平台 摘要 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了基于微信小程序的科创微应用平台的开发全过程。通过分析基于微信小程序的科创微应用平台管理的不足,创建了一个计…

【VMvare虚拟机-Ubuntu】解决内存不足问题

VMvare虚拟机-Ubuntu:解决内存不足问题 1 虚拟机额度磁盘分配2 原因:扩展内存导致无法正常开机3 解决方案:硬盘扩容后无法正常开机3.1 选择镜像文件3.2 设置光盘启动优先3.3 在 live 系统中扩容分区3.4 开启虚拟机 另:VMWare虚拟机…

Java读取损坏的xls表格

Java读取损坏的xls表格 1. 损坏的文件1.1 正常的xls文件用360解压后是这样↓1.2 被损坏的xls文件用360解压后是这样↓ 2. Java代码读取Excel文件分析2.1 使用EasyExcel读取损坏的xls文件报错2.2 使用POI读取损坏的xls文件报错 3. 损坏文件修复方案4. 代码 由于不可抗原因在网站…

C语言中数据类型

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

华为地图服务功能概览 -- HarmonyOS自学7

华为地图服务式Harmony OS生态下的一个地图服务,为开发者提供强大而便捷的地图能力,助力全球开发者实现个性化地图呈现,地图搜索和路线规划功能。 主要包括七大功能:静态图,场景化控件,地点搜索&#xff0c…

【AIGC】CFG:基于扩散模型分类器差异引导

摘要 分类器指导是最近引入的一种方法,在训练后在条件扩散模型中权衡模式覆盖率和样本保真度,在精神上与其他类型的生成模型中的低温采样或截断相同。分类器引导将扩散模型的分数估计与图像分类器的梯度相结合,因此需要训练与扩散模型分离的…

WLAN实验简述

一:配置生产AP1上级接入层交换机LSW3 sys [Huawei]sysname LSW3 [LSW3]undo info-center enable [LSW3]vlan batch 10 100 [LSW3]int g0/0/2 [LSW3-GigabitEthernet0/0/2]port link-type trunk [LSW3-GigabitEthernet0/0/2]port trunk allow-pass vlan 10 100 [LSW…

【爱给网-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 1. 暴力破解密码,造成用户信息泄露 2. 短信盗刷的安全问题,影响业务及导致用户投诉 3. 带来经济损失,尤其是后付费客户,风险巨大,造…

OpenGL笔记二十一之几何类设计

OpenGL笔记二十一之几何类设计 —— 2024-09-16 下午 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记二十一之几何类设计1.运行1.1.立方体运行1.2.球体运行 2.几何类搭建1.立方体分析2.球体分析3.图片资源文件4.关键实现4.1.geometry.h4.2.geometry.cpp…

您使用过哪些AI集成工具提升工作效率

您使用过哪些AI集成工具提升工作效率 随着AI技术的飞速发展,个人开始寻求高效的方法来构建和管理定制化模型,以简化复杂的开发过程,提高工作效率。说起用AI集成工具来提高工作效率,个人作为开发者,确实在使用AI代码辅助…

进口车电子信息单二维码解密

目录 效果 二维码信息 解密后信息 进口车电子信息单二维码解密 效果 二维码信息 QzcOcj0yNsb9cVZsGoZKBOrBbn4RJ6O0N4q9/R10ANBvPgWt1vO75YmnWHsImhQUluNYC/OUYwWiO2IljHAhPmSAm3BieWZpXwi1IGWzLKAkRGkTUpqhT2pwEhkbMKcFsfsBfxh9MT1KRy2YaDvLKwLvOVHp7ZJUh4DdDof6GBGfsvam…

Json和Http专栏

json 理论 什么是JSON? 规则 被大括号包括的是JSON对象,被中括号包括的是JSON数组. JSON数组JSON对象 实验 构建JSON 用代码实现如下json内容: //构建JSON void WirteJson() {QJsonObject rootObject;//1.插入name字段rootObject.insert("name","china&quo…

OpenAI o1:隐含在训练与推理间的动态泛化与流形分布

随着OpenAI o1发布,进一步激发了产业与学术各界对AGI的期待以及new scaling law下的探索热情,也看到来自社区和专业机构对o1的阐释,但总感觉还差点什么,因此决定以自己的角度分篇幅梳理下,并分享给大伙: O…

使用mlp算法对Digits数据集进行分类

程序功能 这个程序使用多层感知机(MLP)对 Digits 数据集进行分类。程序将数据集分为训练集和测试集,创建并训练一个具有两个隐藏层的 MLP 模型。训练完成后,模型对测试数据进行预测,并通过准确率、分类报告和混淆矩阵…

vmvare如何给centos7 设置静态IP地址

本章教程,主要介绍如何在vmvare中如何给虚拟机中设置静态IP地址。本章教程中使用的linux发行版是centos7。 目前没有静态IP地址,并且不能联网,此时我们需要给它配置一个静态IP,并且可以实现联网功能。 一、前置步骤 1、网络设置 2、添加网络 添加一个虚拟机网络,选择VMne…

C++笔记---stack和queue

1. stack的介绍及重要接口 stack---栈,是一种“先进后出,后进先出”的数据结构。 此处的stack是STL库中定义的一个类模板,用于实例化出存储各种类型数据的栈。 bool empty() const;判断栈是否为空(空true/非空false)size_t size() const;返…

Kafka日志索引详解与常见问题分析

目录 一、Kafka的Log日志梳理 1、Topic下的消息是如何存储的? 1. log文件追加记录所有消息 2. index和timeindex加速读取log消息日志 2、文件清理机制 1. 如何判断哪些日志文件过期了 2. 过期的日志文件如何处理 3、Kafka的文件高效读写机制 1. Kafka的文件…