【Android 13源码分析】WindowContainer窗口层级-4-Layer树

news2024/11/15 20:03:59

在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。
对这一块的概念以及相关源码做了详细分析,整理出以下几篇。

【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树

【Android 13源码分析】WindowContainer窗口层级-2-构建流程

【Android 13源码分析】WindowContainer窗口层级-3-实例分析

【Android 13源码分析】WindowContainer窗口层级-4-Surface树

本篇为第四篇,前面三篇已经将Android窗口树介绍完了,但是我们知道安卓真正控制显示的是在SurfaceFlinger层,难道说SurfaceFlinger层也有这么一个窗口树吗?如果有,Framework层构建窗口树的代码这么复杂,难道SurfaceFlinger也有这么一段复杂的逻辑吗?

首先回答第一个问题:SurfaceFlinger层也有这么一个窗口树,严格来说是SurfaceFlinger也有一个对应的Layer树。

在这里插入图片描述
这是使用Winscope工具看到的当前屏幕信息,可以看到在SurfaceFlinger层也有一个和窗口树应用的层级关系,并且在WindowState层下面还多了一级,多出来的这层右边的属性中有一项“isBuffLayer= true”

先以黑盒的形式补充几个Surface相关的知识点,对这块有了解的可跳过。

1. Surface知识黑盒概念

    1. 触发创建Surface时就会触发创建出一个Layer,所以Surface和Layer是一一对应的,只不过在framework层侧重Surface,在SurfaceFlinger侧重Layer。
    1. 应用层只要有Surface,就可以将View的数据绘制保存到Surface中,也就可以显示到屏幕上
    1. Layer有多种类型,最常见的是“容器类型”和“buff类型”,只有“buff类型”的Layer才能保存UI数据。

2 容器类型的创建

前面几篇介绍窗口树的时候,知道那些类其实都是“容器”,作为容器他们本身是没有UI数据的,真正有显示数据的就是 “isBuffLayer= true”的这层Layer。

再回答第二个问题:SurfaceFlinger没有这么复杂构建Layer树的逻辑,因为只要Framework创建一个“容器”类的同时也触发创建一个Surface,这样SurfaceFlinger层就也能同步构造出一个Layer(Surface)树。

2.1 DisplayContent的Surface构建

首先看一下 屏幕(DisplayContent)对应的Surface是怎么创建的。

在构建流程开始的时候就为 DisplayContent的创建了Surface,代码如下:

    # DisplayContent

        private void configureSurfaces(Transaction transaction) {
            // 构建一个SurfaceControl
            final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
                    .setOpaque(true)
                    .setContainerLayer() // 设置为容器类型的Layer
                    .setCallsite("DisplayContent");
            // 设置名字后构建 (Display 0 name="XXX")
            mSurfaceControl = b.setName(getName()).setContainerLayer().build();
            // 设置策略并构建显示区域层次结构
            if (mDisplayAreaPolicy == null) {
                // WMS的getDisplayAreaPolicyProvider方法按返回 DisplayAreaPolicy.Provider
                // 然后其 instantiate的实现 目前只有DisplayAreaPolicy的内部类DefaultProvider
                
                mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
                        mWmService, this /* content */, this /* root */,
                        mImeWindowsContainer);
            }
            ......
        }

在构建窗口树源码分析的时候知道DisplayContent::configureSurfaces 是在 DisplayContent构造方法里执行的,也就是说在构建窗口树的时候,创建了DisplayContent容器的同时,也创建好了对应的Surface,这是第一个映射关系。

注意build的时候 setContainerLayer这个设置, 将其设置为容器类型的Layer

2.2 其他容器Surface的创建与挂载

层级树其他的各个容器创建的也会触发创建出对应的一个Surface,具体的调用链如下:

    WindowContainer::addChild
       WindowContainer::setParent
          WindowState::onParentChanged
                   WindowContainer::createSurfaceControl
                      WindowContainer::setInitialSurfaceControlProperties
                         SurfaceControl.Builder::build
                            SurfaceControl::init

调用链从WindowContainer::addChild 开始是因为每个容器类创建的时候,都会挂载到父节点下,挂载的方式也就是执行WindowContainer::addChild方法,添加到父容器的孩子集合下。

以一个应用的Task容器创建挂载为例:
在这里插入图片描述
假设其他窗口树已经构建好(也已经有了一个对应的Layer树,暂时不知道是怎么来的没关系,稍后就明白了),这个时候应用启动了,肯定是需要创建一个Task,Task创建好后是还是一个单独的容器,这个时候会执行 WindowContainer::addChild 和 WindowContainer::setParent方法,执行完后Task就挂着到窗口树上了。

先看一下这2个方法的代码:

    # WindowContainer

       // 当前容器的孩子容器集合
       protected final WindowList<E> mChildren = new WindowList<E>();

       @CallSuper
       protected void addChild(E child, Comparator<E> comparator) {
          ......
          // 1. 添加到集合中,也就是挂载
          if (positionToAdd == -1) {
                mChildren.add(child);
          } else {
              mChildren.add(positionToAdd, child);
          }
          // Set the parent after we've actually added a child in case a subclass depends on this.
          // 2. 调用孩子容器设置父节点的方法
          child.setParent(this);
       }

首先将子容器添加到 mChildren 集合中,然后调用子容器的 setParent 方法。 这么2步执行后, 孩子与父容器就有绑定关系了,也就是成功挂载到了父节点执行。(细品,其实就是java集合操作)
先看一下这2个方法的代码:

    # WindowContainer

       // 当前容器的孩子容器集合
       protected final WindowList<E> mChildren = new WindowList<E>();

       @CallSuper
       protected void addChild(E child, Comparator<E> comparator) {
          ......
          // 1. 添加到集合中,也就是挂载
          if (positionToAdd == -1) {
                mChildren.add(child);
          } else {
              mChildren.add(positionToAdd, child);
          }
          // Set the parent after we've actually added a child in case a subclass depends on this.
          // 2. 调用孩子容器设置父节点的方法
          child.setParent(this);
       }

首先将子容器添加到 mChildren 集合中,然后调用子容器的 setParent 方法。 这么2步执行后, 孩子与父容器就有绑定关系了,也就是成功挂载到了父节点执行。(细品,其实就是java集合操作)

setParent 方法具体代码如下:

    # WindowContainer
        // 父节点
       private WindowContainer<WindowContainer> mParent = null;

       final protected void setParent(WindowContainer<WindowContainer> parent) {
          if (parent == null) {
             Slog.d(TAG, "setParent old=" + mParent + ",new=" + parent + ",this window=" +
                 this + ",callers=" + Debug.getCallers(6));
          }
          final WindowContainer oldParent = mParent;
          mParent = parent;
             ......
             onParentChanged(mParent, oldParent);
             ......
       }

现在 Task 就成功找到组织了,挂着到窗口树上了。

在这里插入图片描述
但是这个时候,SurfaceFlinger那边还是没变化的,所以继续看后续流程。

    # WindowContainer

       void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent,
             PreAssignChildLayersCallback callback) {
          super.onParentChanged(newParent, oldParent);
          // 正常肯定是有父节点的
          if (mParent == null) {
             return;
          }

          if (mSurfaceControl == null) {
             // If we don't yet have a surface, but we now have a parent, we should
             // build a surface.
             // 父亲有了,但是自身还为null,则触发创建自身Surface的逻辑
             createSurfaceControl(false /*force*/);
          } else {
             ......// 有则进行 reparent
             reparentSurfaceControl(getSyncTransaction(), mParent.mSurfaceControl);
          }
          ......
       }
       // 重点,触发Surface的创建
       void createSurfaceControl(boolean force) {
       setInitialSurfaceControlProperties(makeSurface());
       }

注释比较详细就不多说了,这里肯定是走createSurfaceControl()逻辑,然后注意makeSurface()方法会创建出Surface,然后再调用setInitialSurfaceControlProperties。先看 makeSurface 方法

    # WindowContainer
       // 当前容器的Surface
       protected SurfaceControl mSurfaceControl;

       SurfaceControl.Builder makeSurface() {
          // 拿到父节点,调用makeChildSurface
          final WindowContainer p = getParent();
          // 传递当前,也就是Task
          return p.makeChildSurface(this);
       }

       SurfaceControl.Builder makeChildSurface(WindowContainer child) {
          // 拿到父亲
          final WindowContainer p = getParent();
          // Give the parent a chance to set properties. In hierarchy v1 we rely
          // on this to set full-screen dimensions on all our Surface-less Layers.
          // 调用父亲的makeChildSurface方法,再调用setParent 
          return p.makeChildSurface(child)
                    .setParent(mSurfaceControl);
       }

这里方法虽然不多,但是逻辑有点绕,做一下解释:

    1. 是子容器调用的 makeChildSurface 方法,那子容器类就是 Task,父容器就是 DefaultTaskDisplayArea
    1. 执行 父容器 makeChildSurface方法的时候,又调用了getParent 获取父容器,执行 makeChildSurface,(眉头一皱,事情并不简单)这是开始递归了。
    1. 先不管递归,总之肯定的是 makeChildSurface方法不管怎么递归返回的还是一个SurfaceControl.Builder,然后调用setParent将DefaultTaskDisplayArea的Surface设置为其父节点。

这样一来,结果就是 :Task调用 父容器的makeChildSurface后,创建出了一个Surface,并且挂载到了父容器(DefaultTaskDisplayArea)的下面。
知道结果后,还是要弄清楚 递归方法是怎么创建 Task对应的Surface的

    1. 对于递归调用,最终要的就是找到递归结束的条件,当前这个递归结束的条件就是 DisplayContent 类重写了makeChildSurface方法,也就是说调到 DisplayContent::makeChildSurface 就意味着递归的结束。

DisplayContent作为一个屏幕的最底层的容器,肯定是会调用到的,毕竟 DefaultTaskDisplayArea也是挂载在这个树上的。

所以现在来看看 DisplayContent::makeChildSurface方法

    # DisplayContent
       @Override
       SurfaceControl.Builder makeChildSurface(WindowContainer child) {
          SurfaceSession s = child != null ? child.getSession() : getSession();
          // 创建一个容器类型 Surface的Builder
          final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(s).setContainerLayer();
          if (child == null) {
             return b;
          }
          // 设置容器名
           return b.setName(child.getName())
                   .setParent(mSurfaceControl);
        }

这里的参数 child 就是Task,

    1. 首先创建一个容器类型 SurfaceControl.Builder
    1. 设置name,当前场景是把Task的名字设置过去
    1. 然后设置一下父亲为DisplayContent的Surface

这里要注意,这里设置父节点最终是无效的,会被覆盖掉,因为上面分析看到了把 DefaultTaskDisplayArea设置为Task父容器。从代码的执行顺序上来说,DisplayContent的这次setParent先执行,会被后面的覆盖掉。 从结果来看,Task也确实是挂在DefaultTaskDisplayArea下的。 (不可能每个容器都直接作为DisplayContent的子节点)

调用链执行完了,SurfaceFlinger层也创建并且挂载好了Task的Surface。

在这里插入图片描述
到这里,Framework层的窗口树, SurfaceFlinger的Surface树构建的差不多了,但是手机上还是不会有内容的,为什么呢? 因为这些都是 “容器”,真正的显示需要有Buff类型的Surface。

在这里插入图片描述
再看一次这个图, 对应的窗口树到了WindowState就结束了, SurfaceFliner 这边可以看到WindowState下还有一个节点,这个节点才是真正有UI数据的 Layer。

需要体会一下区别

在Activity启动流程中时执行到目标应用进程创建时会触发Task和ActivityRecord创建和挂载。 这个时候WindowState还没出现,另外到这一步Activity的onCreate也没执行到,所以界面上肯定是没有UI显示的。

Activity进程创建后,会先执行[addWindow流程]触发 WindowState的创建和挂载,但是这步执行完也还是没有画面的, 因为WindowState也是一个“容器”。
真正触发显示图层创建的是在【relayoutWindow】流程,具体的流程不是当前的主题,目前只关注【relayoutWindow】流程中“Buff”类型图层的创建。

3.1 流程概览

这里才是第一次执行relayoutWindow 创建真正显示的surface的地方
relayoutWindow的调用链如下:

WindowManagerService::relayoutWindow
   WindowManagerService::createSurfaceControl
      WindowStateAnimator::createSurfaceLocked -- 创建“Buff” 类型Surface
         WindowStateAnimator::resetDrawState   -- 设置窗口状态为DRAW_PENDING
         WindowSurfaceController::init
            SurfaceControl.Builder::build
               SurfaceControl::init
   WindowSurfaceController::getSurfaceControl  -- 给应用端Surface赋值

开始撸代码

# WindowManagerService 

   public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
         int requestedWidth, int requestedHeight, int viewVisibility, int flags,
         ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
         SurfaceControl outSurfaceControl, InsetsState outInsetsState,
         InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {
         ......
            result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
         ......
      }

createSurfaceControl 方法有4个参数:

  • outSurfaceControl: WMS创建好一个Surface后,还需要返回给应用端用于View的绘制,就是通过这个参数,由参数命名也可以知道这是一个“出参”。
  • result: 方法执行结果
  • win: 当前窗口对应的WindowState,稍后创建Surface会挂载到这个WindowState节点之下
  • winAnimator:WindowStateAnimator对象,管理窗口状态和动画,稍后通过其内部方法创建Surface
# WindowManagerService
   private int createSurfaceControl(SurfaceControl outSurfaceControl, int result,
      WindowState win, WindowStateAnimator winAnimator) {
      // 1. 创建WindowSurfaceController对象
      WindowSurfaceController surfaceController;
      try {
         // 2. 创建“Buff”类型Surface
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
         surfaceController = winAnimator.createSurfaceLocked();
      } finally {
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
      }
      if (surfaceController != null) {
         // 3. 出参给应用端
         surfaceController.getSurfaceControl(outSurfaceControl);
         // 打印日志,outSurfaceControl复制到了framework的值
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);

      }......
      return result;
   }
   

这个方法主要有三步,都是围绕着 WindowSurfaceController 来的:

    1. 先创建出一个WindowSurfaceController 对象 surfaceController
    1. 通过WindowStateAnimator::createSurfaceLocked 对 surfaceController 赋值,根据方法名猜测是创建了一个Surface
    1. 通过 WindowSurfaceController::getSurfaceControl,给应用端 Surface 赋值

这么看来重点是在第二步 WindowStateAnimator::createSurfaceLocked 是如何创建Surface的。

# WindowStateAnimator

   WindowSurfaceController mSurfaceController;
   // WindowState的状态
   int mDrawState;

   WindowSurfaceController createSurfaceLocked() {
      final WindowState w = mWin;
         if (mSurfaceController != null) {
            return mSurfaceController;
      }

      w.setHasSurface(false);
      // 打印窗口状态
      ProtoLog.i(WM_DEBUG_ANIM, "createSurface %s: mDrawState=DRAW_PENDING", this);
      // 重点* 1. 重置窗口状态
      resetDrawState();
      ......
         // 重点* 2. 创建WindowSurfaceController
         mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
                    flags, this, attrs.type);
      ......
      return mSurfaceController;
   }

这里有2个重点:

    1. 设置窗口状态为 DRAW_PENDING
    1. 创建Surface

3.2 设置窗口状态–DRAW_PENDING

# WindowStateAnimator
   void resetDrawState() {
      // 设置windowState状态为DRAW_PENDING
      mDrawState = DRAW_PENDING;

      if (mWin.mActivityRecord == null) {
         return;
      }

      if (!mWin.mActivityRecord.isAnimating(TRANSITION)) {
         mWin.mActivityRecord.clearAllDrawn();
      }
   }

WindowState有很多状态,以后会单独说,这里需要注意

  1. WindowState状态是保存在WindowStateAnimator中
  2. WindowStateAnimator::createSurfaceLocked方法会将WindowState状态设置为DRAW_PENDING,表示等待绘制。

3.3 创建“Buff”类型Surface

继续回到主流程,看看 WindowSurfaceController 的构造方法

# WindowSurfaceController

   SurfaceControl mSurfaceControl;

    WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
            int windowType) {
      mAnimator = animator;
      // 1. 也会作为Surface的name
      title = name;

      mService = animator.mService;
      // 2. 拿到WindowState
      final WindowState win = animator.mWin;
      mWindowType = windowType;
      mWindowSession = win.mSession;

      Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
      // 3. 重点* 构建Surface(也是通过makeSurface 方法)
      final SurfaceControl.Builder b = win.makeSurface()
               .setParent(win.getSurfaceControl()) // 设置为父节点
               .setName(name)
               .setFormat(format)
               .setFlags(flags)
               .setMetadata(METADATA_WINDOW_TYPE, windowType)
               .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
               .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
               .setCallsite("WindowSurfaceController");

      final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags
               & WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0);
      // 高版本都为BLAST
      if (useBLAST) {
         // 4. 重点* 设置为“Buff”图层
         b.setBLASTLayer();
      }
      // 触发build
      mSurfaceControl = b.build();
      Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }

这个方法有4个点

  1. 第一个参数传递的字符串最终也会作为Surface的name
  2. 获取到WindowState对象,后面会设置为创建Surface的父节点
  3. 构建出一个Surface对象, 注意name和 父节点的设置。 另外可以知道也是通过makeSurface()方法构建的, 这个方法在 2.1小结看到是构建出一个“容器”类型的Surface。
  4. 将Surface设置为“Buff”类型,这个非常重要,因为上一步默认还是“容器”类型,所以需要设置成“Buff”类型,再后面就是build出一个Surface了

那么到这里Surface的创建就完成了,这里可能有的人如果对Surface知识不太清楚的话会比较迷糊,WindowSurfaceController,SurfaceController,Surface到底是什么关系,这个不在当前流程的重点,暂且理解为同级吧,有WindowSurfaceController就可以拿到内部的SurfaceController,而SurfaceController又可以获取到Surface。

3.4 返回Surface到应用端

最后再来看一下 WMS这边创建好后的Surface是如何设置给应用端的。

应用端View的绘制信息都是保存到Surface上的,因为必定要有一个"Buff"类型的Surface,也就是上面流程中创建的这个Surface。

应用端的ViewRootImpl触发WMS的relayoutWindow会传递一个出参 :outSurfaceControl过来, 现在WMS会通过以下方法将刚刚创建好是Surface传递到应用端。

这样一来应用端就有了可以保持绘制数据的Surface,然后就可以执行 View::draw。

# WindowSurfaceController
    void getSurfaceControl(SurfaceControl outSurfaceControl) {
      // 将framework层的SurfaceControl copy给应用层传递过来的outSurfaceControl
      outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
    }

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

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

相关文章

计算机的错误计算(九十五)

摘要 从计算机的错误计算&#xff08;八十六&#xff09;至&#xff08;九十四&#xff09;&#xff0c;其主题均涉及对数运算。本节用错数解释&#xff08;九十四&#xff09;中的错误计算的原因。其余类似。 首先&#xff0c;由计算机的错误计算&#xff08;二十七&#xf…

QUIC的loss detection学习

PTO backoff backoff 补偿 /ˈbkɒf/PTO backoff 是QUIC&#xff08;Quick UDP Internet Connections&#xff09;协议中的一种机制&#xff0c;用于处理探测超时&#xff08;Probe Timeout, PTO&#xff09;重传策略 它逐步增加探测超时的等待时间&#xff0c;以避免网络拥塞…

外网(公网)访问VMware workstation 虚拟机内web网站的配置方法---端口转发总是不成功的原因

问题背景&#xff1a;客户提供的服务器操作系统配置web程序时&#xff0c;总是显示莫名其妙的问题&#xff0c;发现是高版本操作系统的.net库已经对低版本.net库进行了大范围修订&#xff0c;导致在安全检测上、软件代码规范上更加苛刻&#xff0c;最终导致部署不成功。于是想到…

使用Qt 搭建简单雷达

目录 1.简易雷达图思维导图 2.结果展示图 3.制作流程 3.1表盘的绘制 3.1.1 绘制底色 ​编辑 3.1.2 绘制大圆 3.3.3绘制小圆 3.3.4 绘制小圆的内容 3.3.5 绘制表盘刻度和数字标注 3.3.6 绘制指针 3.3.7 绘制扇形 3.2 设置定时器让表盘动起来 3.3.1 设置动态指针…

Excel图片批量插入单元格排版处理插件【图片大师】

为了方便大家在图片的插入排版的重复工作中解放出来&#xff0c;最近发布了一款批量插入图片的插件&#xff0c;欢迎大家下载&#xff0c;免费试用。 这是图片的文件夹&#xff1a; 主要功能如下: 1&#xff0c;匹配单元格名称的多张图批量插入到一个单元格 该功能支持设置图…

【1.使用Index和Match函数自动补全内容】

目录 前言如何利用函数自动填充内容效果学会使用的方法(文字图片版本)只管使用&#xff0c;不看原理原理解读MATCH函数INDEX函数组合 学会使用的方法(视频版本) 后言最后想说的话 前言 如何利用函数自动填充内容 先说结论&#xff0c;本文的目的是通过使用Excel的函数&#xf…

深度强化学习Reinforcement Learning|PG|Actor-Critic|A3C|DDPG

目录 一、PG(Policy Gradient)策略梯度算法&#xff08;on-policy&#xff09; 1、策略梯度公式推导 2、代码讲解/伪代码 3、改进 3.1Trick Baseline 3.2 Suitable Credit 二、Actor-Critic算法 三、A3C算法 四、DDPG算法 前言 我们都知道强化学习环境env的不确定性是…

TCP协议分析《实验报告》

一、实验目的 1、理解TCP协议&#xff1b; 2、掌握TCP协议三次握手建立连接和四次挥手释放连接的过程&#xff1b; 3、理解TELNET协议及工作过程&#xff1b; 4、掌握TCP协议分析方法。 二、实验设备和环境 1、硬件设备&#xff1a;PC机或笔记本电脑&#xff1b; 2、软件…

Matlab simulink建模与仿真 第十七章(补充离散库和补充数学库)

参考视频&#xff1a;simulink1.1simulink简介_哔哩哔哩_bilibili 一、补充离散库和补充数学库中的模块概览 1、补充离散库 注&#xff1a;每个版本的补充离散库不一定相同&#xff0c;也不是每个版本的库都有如上所有模块。 2、补充数学库 二、离散直接传递函数Ⅱ模块 1、…

OpenCV_图像旋转超详细讲解

图像转置 transpose(src, dst); transpose()可以实现像素下标的x和y轴坐标进行对调&#xff1a;dst(i,j)src(j,i)&#xff0c;接口形式 transpose(InputArray src, // 输入图像OutputArray dst, // 输出 ) 图像翻转 flip(src, dst, 1); flip()函数可以实现对图像的水平翻转…

re题(24)BUUFCTF-[WUSTCTF2020]level1

BUUCTF在线评测 (buuoj.cn) 放到ida 这是下载的文本 逻辑比较简单&#xff0c;写个脚本 p[198,232,816,200,1536,300,6144,984,51200,570,92160,1200,565248,756,1474560,800,6291456,1782,65536000] for i in range(1,20):if (i & 1) ! 0 :p[i-1]chr(p[i-1] >> i)…

C++ ——string的模拟实现

目录 前言 浅记 1. reserve&#xff08;扩容&#xff09; 2. push_back&#xff08;尾插&#xff09; 3. iterator&#xff08;迭代器&#xff09; 4. append&#xff08;尾插一个字符串&#xff09; 5. insert 5.1 按pos位插入一个字符 5.2 按pos位插入一个字符串 …

C++速通LeetCode简单第18题-杨辉三角(全网唯一递归法)

全网唯一递归法&#xff1a; vector<vector<int>> generate(int numRows) {vector<int> v;vector<vector<int>>vn;if (numRows 1){v.push_back(1);vn.push_back(v);v.clear();return vn;//递归记得return}if (numRows 2){v.push_back(1);vn.p…

FPGA与Matlab图像处理之伽马校正

文章目录 一、什么是伽马校正&#xff1f;二、伽马校正的由来三、Matlab实现伽马校正3.1 matlab代码3.2 matlab结果 四、Verilog实现伽马校正4.1 生成初始化ROM文件4.2 Verilog代码4.3 仿真结果 一、什么是伽马校正&#xff1f; Gamma校正是图像处理中用以调整图像的亮度和对比…

代码随想录冲冲冲 Day47 单调栈Part1

739. 每日温度 初步了解单调栈&#xff0c;根本思想就是如果求的是一个元素右边或左边第一个最大的元素 那么就是递增栈&#xff0c;如果是最小的就是递减栈 首先先放入一个0代表 第一个元素的index 之后开始for loop 当后面的值小于等于这个top时 就要先把index放入st中&a…

Leetcode 找到字符串中所有字母异位词

在 C 中&#xff0c;两个 vector<int> 类型的变量进行 操作时&#xff0c;会逐个比较它们的元素&#xff0c;只有当两个向量的长度相同且每个位置上的元素值都相同时&#xff0c; 操作才会返回 true。 因此&#xff0c;在这道题的代码中&#xff0c;sCount pCount 这一…

Flip动画的实现示例demo

Flip动画的实现示例demo 文章说明核心代码效果展示Flip动画工具类的封装 文章说明 文章主要为了学习flip动画的实现思路&#xff0c;并且采用此示例效果来理解该实现思路的含义 参考渡一前端袁老师的讲解视频 核心代码 采用简单的y轴变化的动画效果为示例 <!DOCTYPE html>…

【K230 实战项目】气象时钟

【CanMV K230 AI视觉】 气象时钟 功能描述&#xff1a;说明HMDI资源3.5寸屏幕 使用方法 为了方便小伙伴们理解&#xff0c;请查看视频 B站连接 功能描述&#xff1a; 天气信息获取&#xff1a;通过连接到互联网&#xff0c;实时获取天气数据&#xff0c;包括温度、湿度、天气状…

【STM32】独立看门狗(IWDG)原理详解及编程实践(上)

本篇文章是对STM32单片机“独立看门狗&#xff08;IWDG&#xff09;”的原理进行讲解。希望我的分享对你有所帮助&#xff01; 目录 一、什么是独立看门狗 &#xff08;一&#xff09;简介 &#xff08;二&#xff09;、独立看门狗的原理 &#xff08;三&#xff09;、具体操…

vulkano (rust) 画一个三角形 (vulkan 渲染窗口初始化 (Linux) 下篇)

上文说到, vulkan 相比 OpenGL (ES), 更加贴近底层硬件, 许多东西需要应用软件手动管理, 所以 vulkan 的初始化过程比较麻烦, 或者说学习曲线比较陡峭. 但是, 这种麻烦是一次性的, 一旦学会了, 就能开始享受 vulkan 的诸多好处啦 ~ 本文以绘制一个三角形为例, 介绍 vulkan 的初…