Android硬件渲染流程

Android硬件渲染流程

  • 一.渲染流程
    • 1.VSync信号的监听
    • 2.VSync信号触发绘制
  • 二.渲染原理
    • 1.画布的获取
      • 1.1 画布的创建
      • 1.2 渲染指令列表的创建
    • 2.绘制与渲染指令
      • 2.1 矩形的绘制
      • 2.2 硬件渲染指令
      • 2.3 节点的绘制
    • 3.绘制的提交
      • 3.1 绘制结果的保存
      • 3.2 绘制结果的获取
    • 4.层级的构建
      • 4.1 绘制结果的更新
      • 4.2 构建的分发
      • 4.3 构建数据的保存
    • 5.渲染与渲染管线
      • 5.1 节点的更新
      • 5.2 节点的渲染
  • 三.总结
    • 1.硬件渲染
    • 2.绘制流程
      • 2.1 渲染指令
      • 2.2 对应关系
    • 3.渲染流程
      • 3.1 渲染指令列表的获取
      • 3.2 渲染层级的构建
      • 3.3 渲染管线的渲染

一.渲染流程

1.VSync信号的监听

    在Android中,App的渲染流程是从ViewRootImpl开始的。在回调Activity的onResume方法后,会调用ViewRootImpl的requestLayout方法触发页面中View的测量与绘制。

    在requestLayout方法中,首先会调用checkThread方法检查当前线程是否为UI线程,如果不是,则抛出异常。接下来会调用scheduleTraversals方法。
0
    在ViewRootImpl的scheduleTraversals方法中,主要做了两件事:

1)向主线程的消息队列发送一个同步信息屏障。

2)提交callbackType类型为CALLBACK_TRAVERSAL的TraversalRunnable。
1

2.VSync信号触发绘制

    当VSync信号到来时,会执行TraversalRunnable的run方法,该方法内部会调用ViewRootImpl的doTraversal方法。
2
    在ViewRootImpl的doTraversal方法中,主要做了两件事:

1)移除主线程消息队列的同步信息屏障。

2)调用performTraversals方法。
3
    在ViewRootImpl的performDraw方法中,会调用draw方法。在ViewRootImpl的draw方法中,如果开启了硬件渲染,就会从mAttachInfo的mThreadedRenderer中获取ThreadedRenderer。并调用ThreadedRenderer的draw方法。
4
    在ThreadedRenderer的draw方法中主要做了两件事:

1)从DecorView开始递归构建DisplayList。

2)唤醒Render线程对DisplayList进行渲染。
5
    在ThreadedRenderer的updateRootDisplayList中主要做了四件事:

1)从DecorView开始向下分发draw方法,递归构建DisplayListOp。

2)获取最顶层的RecordingCanvas。

3)通过DecorView获取最终的RenderNode并绘制到RecordingCanvas上。

4)将DisplayListOp填充到Native层的RootRenderNode中。
6

二.渲染原理

1.画布的获取

    在硬件渲染中,每个View都有一个RenderNode。当调用RenderNode的beginRecording方法时,内部会调用RecordingCanvas的静态方法obtain获取RenderNode。
7    在RecordingCanvas的静态方法obtain中,会创建RecordingCanvas。
8
    在RecordingCanvas的构造方法中,主要做了两件事:

1)创建Native层Canvas并返回对应的地址。

2)调用父类的构造方法对返回的地址进行保存。
9

1.1 画布的创建

    RecordingCanvas的nCreateDisplayListCanvas方法对应的Native实现为android_graphics_DisplayListCanvas的android_view_DisplayListCanvas_createDisplayListCanvas函数。

    在android_view_DisplayListCanvas_createDisplayListCanvas函数中,主要做了三件事:

1)获取Native层的RenderNode。

2)创建Canvas。

3)返回Canvas对应的地址。
10
    在Canvas::create_recording_canvas方法中,会创建SkiaRecordingCanvas。
11
    在SkiaRecordingCanvas的构造方法中,会调用initDisplayList方法,初始化DisplayList。
12

1.2 渲染指令列表的创建

    在SkiaRecordingCanvas的initDisplayList方法中,主要做了三件事:

1)清除上一帧的SkiaDisplayList,如果SkiaDisplayList为空,则再创建一个SkiaDisplayList。

2)绑定SkiaDisplayList与RecordingCanvas。

3)将RecordingCanvas保存到SkiaCanvas中。
13
    在SkiaDisplayList的attachRecorder方法中,会将SkiaDisplayList中的SkiaDisplayData与RecordingCanvas绑定。
14

2.绘制与渲染指令

2.1 矩形的绘制

    在硬件绘制过程中,当调用Canvas的drawRect方法时,在Canvas的drawRect方法中,会调用nDrawRect方法。
15
    Canvas的nDrawRect方法对应的Native实现为android_graphics_Canvas的drawRect函数。在drawRect函数中,主要做了两件事:

1)获取Native层Canvas。

2)通过Canvas绘制矩形。
16
    SkiaRecordingCanvas继承自SkiaCanvas。这里的drawRect方法在SkiaRecordingCanvas的父类SkiaCanvas中实现。

    在SkiaCanvas的drawRect方法中,会调用SkCanvas的drawRect方法。
17
    在SkCanvas的drawRect方法中,会调用子类RecordingCanvas的onDrawRect方法。
18
    在RecordingCanvas的onDrawRect方法中,会调用之前保存的DisplayListData的drawRect方法。在DisplayListData的drawRect方法中,会创建一个DrawRect指令并保存。
19

2.2 硬件渲染指令

    在DisplayListData中,所有的绘制指令都存储在一块连续的内存中。

    在Android中,所有的绘制指令都继承了Op。Op是一个结构体,在Op中有两个字段,type表示指令的类型,skip表示指令的长度。

struct Op {uint32_t type : 8;uint32_t skip : 24;};

    在DisplayListData的push方法中,主要做了四件事:

1)计算当前指令的长度。

2)判断所有的绘制指令是否超过内存最大容量,如果超过最大容量,则进行扩容,重新分配内存,每次扩容增加4096个字节。

3)计算当前绘制指令在内存中的位置,并在指定位置处创建当前指令的实例对象。

4)为当前绘制指令的type和skip赋值。
20

2.3 节点的绘制

    在ThreadedRenderer的updateViewTreeDisplayList方法中,会调用View的updateDisplayListIfDirty方法。
21    在View的updateDisplayListIfDirty方法中,主要做了三件事:

1)从自身的RenderNode中获取RecordingCanvas。

2)通过Flag判断,如果是ViewGroup且自身不用绘制,则分发子View去绘制,否则直接绘制。

3)结束绘制。
22
    在View的draw方法中,首先会绘制自身。然后对子View进行绘制。dispatchDraw方法在View中为空实现,如果一个View不是ViewGroup,那么dispatchDraw方法不会对任何View进行绘制分发。
23
    View的dispatchDraw方法在ViewGroup中被重写。如果一个View是ViewGroup,那么dispatchDraw方法会对子View进行绘制分发。在ViewGroup的dispatchDraw方法中,会调用drawChild方法。
24
    在ViewGroup的drawChild方法中,会调用View的draw方法。这里的draw方法是View中一个重载的draw方法,只在ViewGroup中调用。
25
    在View重载的draw方法中,主要做了三件事:

1)判断是否开启硬件渲染。

2)如果开启硬件渲染,则对当前View构建DisplayList,保存到当前View的RenderNode并返回。

3)如果当前View的DisplayList不为空,则将当前View的RenderNode绘制到父View的Canvas上。
26
    在RecordingCanvas的drawRenderNode方法中,会调用nDrawRenderNode方法。
27
    RecordingCanvas的nDrawRenderNode方法对应的Native层实现为android_graphics_DisplayListCanvas的android_view_DisplayListCanvas_drawRenderNode函数。

    在android_view_DisplayListCanvas_drawRenderNode函数中,主要做了三件事:

1)根据地址获取Native层Canvas。

2)根据地址获取View对应的Native层的RenderNode。

3)将RenderNode绘制到Canvas上。
28
    根据硬件渲染中Canvas的创建过程可以知道,这里的Canvas实际上是SkiaRecordingCanvas。

    在SkiaRecordingCanvas的drawRenderNode方法中,主要做了两件事:

1)将RenderNode封装成RenderNodeDrawable,保存到SkiaDisplayList中用于记录。

2)对封装好的RenderNodeDrawable进行绘制。
29
    SkiaRecordingCanvas继承自SkiaCanvas。这里的drawDrawable方法在SkiaRecordingCanvas的父类SkiaCanvas中实现。

    在SkiaCanvas的drawDrawable方法中,会调用SkCanvas的drawDrawable方法。
30
    在SkCanvas的drawDrawable方法中,会调用子类RecordingCanvas的onDrawDrawable方法。
31
    在RecordingCanvas的onDrawDrawable方法中,会调用之前保存的DisplayListData的drawDrawable方法。在DisplayListData的drawDrawable方法中,会创建一个DrawDrawable指令并保存。
32

3.绘制的提交

3.1 绘制结果的保存

    在RenderNode的endRecording方法中,主要做了两件事:

1)停止记录,标记DisplayList可用。

2)释放RecordingCanvas。
33
    在RecordingCanvas的finishRecording方法中,会调用nFinishRecording方法。
34
    RecordingCanvas的nFinishRecording方法对应的Native实现为android_graphics_DisplayListCanvas的android_view_DisplayListCanvas_finishRecording函数。

    在android_view_DisplayListCanvas_finishRecording函数中,主要做了三件事:

1)获取Canvas,实际获取的是SkiaRecordingCanvas。

2)获取RenderNode。

3)调用SkiaRecordingCanvas的finishRecording结束绘制。
35
    在SkiaRecordingCanvas的finishRecording方法中,主要做了三件事:

1)暂停标记并获取SkiaDisplayList。

2)将SkiaDisplayList封装成DisplayList,DisplayList是对SkiaDisplayListWrapper的重命名,SkiaDisplayListWrapper会保存SkiaDisplayList。

3)将DisplayList保存到RenderNode中。
36

3.2 绘制结果的获取

    在DisplayList构建完成后, 会调用ThreadedRenderer的syncAndDrawFrame方法唤醒Render线程进行渲染。在ThreadedRenderer的syncAndDrawFrame中,会调用nSyncAndDrawFrame方法。
37
    ThreadedRenderer的nSyncAndDrawFrame方法对应的Native实现为android_graphics_HardwareRenderer的android_view_ThreadedRenderer_syncAndDrawFrame函数。

    在android_view_ThreadedRenderer_syncAndDrawFrame函数中,主要做了两件事:

1)获取RenderProxy。

2)调用RenderProxy的syncAndDrawFrame方法。
38
    在RenderProxy的syncAndDrawFrame方法中,会调用DrawFrameTask的drawFrame方法。在DrawFrameTask的drawFrame方法中,会调用postAndWait方法。
39
    在DrawFrameTask的postAndWait方法中,主要做了三件事:

1)对DrawFrameTask的run方法进行封装。

2)将封装后的对象添加到RenderThread的队列中。

3)UI线程进入阻塞状态。
40
    当RenderTread执行任务时,会调用DrawFrameTask的run方法。在DrawFrameTask的run方法中,主要做了三件事:

1)获取UI线程构建的DisplayList。

2)唤醒UI线程。

3)根据DisplayList进行绘制。
41

4.层级的构建

    在DrawFrameTask的syncFrameState方法中,主要做了两件事:

1)处理硬件加速层,如TextureView的绘制。

2)构建TreeInfo。
42
    在CanvasContext的prepareTree方法中,主要做了两件事:

1)保存LayerUpdateQueue到TreeInfo中,为后续后构建TreeInfo做准备。LayerUpdateQueue用于保存待绘制的RenderNode。

2)遍历RenderNode构建TreeInfo。
43
    在RenderNode的prepareTreeImpl方法中,主要做了三件事:

1)获取DisplayList。

2)通过DisplayList分发子节点构建TreeInfo。

3)将完成构建的当前RenderNode保存到LayerUpdateQueue中。
44

4.1 绘制结果的更新

    在RenderNode的pushStagingDisplayListChanges方法中,会调用syncDisplayList方法,对DisplayList进行锁定保存。
45
    在RenderNode的syncDisplayList方法中,主要做了三件事:

1)遍历mStagingDisplayList中保存的RenderNode,对RednerNode的引用加1。mStagingDisplayList是用于暂存本次构建好的DisplayList的变量。

2)遍历mDisplayList中保存的RenderNode,对RednerNode的引用减1,并清空mDisplayList中对SkiaDisplayList的引用。mDisplayList是用于保存下次待渲染的DisplayList的变量。

3)将本次构建好的DisplayList保存到下次待渲染的DisplayList。
46
    DisplayList是对SkiaDisplayListWrapper的重命名。在SkiaDisplayListWrapper的updateChildren方法中,会调用SkiaDisplayList的updateChildren方法。
47
    在SkiaDisplayList的updateChildren方法中,主要做了三件事:

1)遍历获取RenderNodeDrawable。

2)从RenderNodeDrawable中获取RenderNode。

3)将RenderNode作为参数,执行参数中传入的function方法。
48

4.2 构建的分发

    DisplayList是对SkiaDisplayListWrapper的重命名。在SkiaDisplayListWrapper的prepareListAndChildren方法中,会调用SkiaDisplayList的prepareListAndChildren方法。
49
    在SkiaDisplayList的prepareListAndChildren方法中,主要做了四件事:

1)遍历获取RenderNodeDrawable。

2)从RenderNodeDrawable中获取RenderNode。

3)对TreeInfo中的属性进行更新。

4)将RenderNode和TreeInfo作为参数,执行参数中传入的function方法。
50
    这里传入的function是RenderNode的prepareTreeImpl方法,这样就实现了子RenderNode构建TreeInfo。

4.3 构建数据的保存

    在RenderNode的pushLayerUpdate方法中,会对当前的RenderNode进行保存。
51

5.渲染与渲染管线

    在Android中,渲染管线有两种:SkiaOpenGLPipeline和SkiaVulkanPipeline,底层实现分别对应着OpenGL和Vulkan。下面所有的IRenderPipeline以SkiaOpenGLPipeline为例。

    在CanvasContext的draw方法中,主要做了三件事:

1)获取可绘制的缓存。

2)使用GPU按照绘制指令绘制界面。

3)将绘制好的图形缓冲通过Binder交给SurfaceFlinger进行合成与显示,即上帧。
52
    在SkiaOpenGLPipeline的draw方法中,主要做了两件事:

1)创建SkSurface指针并初始化。

2)开始渲染。
53

    SkiaOpenGLPipeline的renderFrame方法在SkiaOpenGLPipeline的父类SkiaPipeline中实现。

    在SkiaPipeline的renderFrame方法中,主要做了四件事:

1)获取SkCanvas。

2)处理发生变化的Layer,更新对应RenderNode。

3)对所有的renderNode进行绘制。

4)释放SkCanvas。
54

5.1 节点的更新

    在SkiaPipeline的renderLayersImpl方法中,主要做了三件事:

1)遍历LayerUpdateQueue,获取Entry,并从Entry中获取RenderNode.

2)从RenderNode中获取SkCanvas。

3)将RenderNode分装成RenderNodeDrawable,并绘制到SkCanvas上。
55
    RenderNodeDrawable继承自SkDrawable。在RenderNodeDrawable的draw方法中,会调用onDraw方法,onDraw方法在RenderNodeDrawable被重写。在RenderNodeDrawable的onDraw方法中,会调用forceDraw方法。在RenderNodeDrawable的forceDraw方法中,会调用drawContent方法。
56
    在RenderNodeDrawable的drawContent方法中,主要做了两件事:

1)从RenderNode中获取DisplayList。

2)绘制DisplayList。
57
    在SkiaDisplayList的draw方法中,会调用DisplayListData的draw方法。
58
    在DisplayListData的draw方法中,会调用map方法。在DisplayListData的map方法中,主要做了五件事:

1)计算绘制指令内存中绘制指令的终止区域。

2)从绘制指令内存的起始位置进行遍历。

3)获取绘制指令的类型和长度。

4)根据指令的类型,调用指令的绘制方法。

5)根据指令的长度,计算下一个绘制指令的位置。
59
    Array<Fn>是模版生成的代码,其中的模版参数Fn代表不同类型的绘制指令的draw方法的调用。以DrawRect指令为例,当ptr指针指向DrawRect指令时,会根据指令类型从Array<Fn>取出对应DrawRect的draw方法调用的Fn,当调用Fn时,会触发DrawRect的draw方法的执行。

    在DrawRect的draw方法中,会通过SkCanvas的draw方法完成绘制。
60

5.2 节点的渲染

    在SkiaPipeline的renderFrameImpl方法中,主要做了两件事:

1)对RenderNode进行遍历,将RenderNode封装成RenderNodeDrawable。

2)对RenderNodeDrawable进行渲染。
61
    与renderLayerImpl不同,renderFrameImpl绘制到缓存对应的SkCanvas上,而不是自身的SkCanvas上。

三.总结

1.硬件渲染

    Android硬件渲染分成两个部分:渲染指令列表的构建和渲染指令列表的渲染,分别对应着ThreadedRenderer的updateRootDisplayList方法和syncAndDrawFrame方法。即绘制过程和渲染过程是分开的。

    绘制过程发生在UI线程,渲染过程发生在Render线程。

2.绘制流程

    在硬件渲染中,每个View对应着一个RenderNode。每个RenderNode中保存着对应的SkiaDisplayList。

    硬件的绘制过程,本质上是将待绘制的数据封装成对应的硬件渲染指令,保存到SkiaDisplayList中。子View绘制到完成后,父View会对子View的RenderNode进行绘制,将子View的RenderNode绘制指令保存到SkiaDisplayList中,形成绘制的层级结构。

    画布的获取本质上是对SkiaDisplayList的创建与初始化。

    绘制的提交本质上是将构建好的SkiaDisplayList保存到RenderNode中。

2.1 渲染指令

    在Android中,所有的绘制指令都继承了Op。Op是一个结构体,在Op中有两个字段,type表示指令的类型,skip表示指令的长度。

struct Op {uint32_t type : 8;uint32_t skip : 24;};

    所有的渲染指令都保存到DisplayListData中,DisplayListData本质上是一块连续的内存。

2.2 对应关系

    SkiaRecordingCanvas继承自SkiaCanvas,RecordingCanvas继承自SkCanvas。

    RecordingCanvas负责管理硬件渲染指令和DisplayListData、操作硬件渲染指令在DisplayListData上的填充。SkiaRecordingCanvas是对RecordingCanvas的封装,负责DisplayListData的大小分配与初始化、SkiaDisplayList的管理和子View的RenderNode更新保存。

    RecordingCanvas直接操作DisplayListData,SkiaRecordingCanvas直接操作SkiaDisplayList。SkiaDisplayList是对DisplayListData的屏蔽封装。

3.渲染流程

    硬件的渲染过程分成三部分:渲染指令列表(SkiaDisplayList)的获取、渲染层级(TreeInfo)的构建、渲染管线(IRenderPipeline)的渲染。

3.1 渲染指令列表的获取

    渲染指令列表的获取过程会阻塞UI线程,在获取完成后会唤醒UI线程。

3.2 渲染层级的构建

    渲染层级的构建过程本质上是从DecorView的RootRenderNode开始向下判断哪些子View的RenderNode发生了变化,并把变化的RenderNode保存到TreeInfo中。

3.3 渲染管线的渲染

    在Android中,渲染管线有两种:SkiaOpenGLPipeline和SkiaVulkanPipeline,底层实现分别对应着OpenGL和Vulkan。

    渲染管线的渲染过程分成两部分:RenderNode的更新渲染和RenderNode的最终渲染。

    渲染管线在渲染过程中会将RenderNode封装成RenderNodeDrawable。RenderNode的更新渲染本质上就是在RenderNode自身的SkCanvas上绘制。RenderNode的最终渲染本质上就是在生产消费模型对应的SkCanvas上绘制。

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

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

相关文章

解决Vue3+TS+vite,VSCode 高亮语法错误

一般像这种提示&#xff0c;有可能就是TypeScript语法的识别问题&#xff0c; 一般我们重装一下Vue - Official插件 或者将tcconfig.json中的moduleResolution改为node模式&#xff0c; 基本都是TypeScript无法识别vue文件中的TypeScript语句导致的

单片机原理及技术(二)—— AT89S51单片机(一)(C51编程)

目录 一、AT89S51单片机的片内硬件结构 二、AT89S51的引脚功能 2.1 电源及时钟引脚 2.2 控制引脚 2.3 并行 I/O口引脚 三、AT89S51的CPU 3.1 运算器 3.1.1 算术逻辑单元&#xff08;ALU&#xff09; 3.1.2 累加器A 3.1.3 程序状态字寄存器&#xff08;PSW&#xff09…

JRT1.7发布

JRT1.7连仪器在线演示视频 JRT1.5实现质控主体、1.6基本完成质控&#xff1b;本次版本推进到1.7&#xff0c;1.7集菜单权限、登录、打印导出客户端、初始化、质控、Linux客户端、仪器连接和监控体系各种功能大全&#xff0c;上十年写系统用到的都全了。 这次直接挑战检验最难…

【微服务】springboot 构建镜像多种模式使用详解

目录 一、前言 二、微服务常用的镜像构建方案 3.1 使用Dockerfile 3.2 使用docker plugin插件 3.3 使用docker compose 编排文件 三、环境准备 3.1 服务器 3.2 安装JDK环境 3.2.1 创建目录 3.2.2 下载安装包 3.2.3 配置环境变量 2.2.4 查看java版本 3.3 安装maven …

加拿大媒体广告投放:媒体宣发主流媒体《金融邮报》《埃德蒙顿日报》

介绍《埃德蒙顿日报》与《埃德蒙顿太阳报》 在加拿大阿尔伯塔省首府埃德蒙顿&#xff0c;有两份主流新闻类报纸。其中&#xff0c;《埃德蒙顿日报》是加拿大主要英文报纸之一&#xff0c;也被称为爱蒙顿新闻报。而另一份报纸则是《埃德蒙顿太阳报》&#xff0c;是加拿大阿尔伯…

Linux操作系统最著名的两大系列Red Hat和Debian

Linux操作系统可以根据其背后的项目或社区分为不同的系列&#xff0c;其中最著名的两大系列是Red Hat系列和Debian系列。 1.著名的两大系列是Red Hat和Debian Red Hat系列&#xff1a; Red Hat Enterprise Linux (RHEL)&#xff1a;这是Red Hat公司推出的企业级操作系统&#…

香橙派AIpro(OrangePi AIPro)开发板初测评

开发板简介 最近&#xff0c;我拿到手一款Orange Pi AI Pro 开发板&#xff0c;它是香橙派联合华为精心打造的高性能AI 开发板&#xff0c;最早发布于2023年12月&#xff0c;其搭载了昇腾AI 处理器&#xff0c;可提供8TOPS INT8 的计算能力&#xff0c;内存提供了8GB 和16GB两…

【C++】---多态

【C】---多态 一、多态的概念二、多态的定义及实现1、构成多态的2个必要条件2、什么叫做虚函数的重写&#xff1f;3、虚函数重写的3个例外4、建议把 析构函数 都定义为&#xff1a;虚函数 三、C11的两个关键字&#xff1a;final override1、final&#xff1a;修饰虚函数&#x…

Spring—Spring配置文件概念及应用(实现一个图形验证码)

文章目录 配置文件配置文件作用配置文件的格式配置文件优先级说明配置文件书写代码的格式yml文件代码的格式 Value注解 properties 缺点分析properties VS yml实现一个验证码程序 配置文件 配置文件作用 整个项目的重要信息我们都会配置在配置文件中&#xff0c;比如说我们数…

VMware虚拟机-设置系统网络IP、快照、克隆

1.设置网络IP 1.点击右上角开关按钮-》有线 已连接-》有线设置 2.手动修改ip 3.重启或者把开关重新关闭开启 2.快照设置 快照介绍&#xff1a; 通过快照可快速保存虚拟机当前的状态&#xff0c;后续可以使用虚拟机还原到某个快照的状态。 1.添加快照(需要先关闭虚拟机) 2.在…

电子商务网站(网上商店PetShop)

PetShop是一个范例&#xff0c;微软用它来展示.Net企业系统开发的能力。PetShop随着版本的不断更新&#xff0c;至现在基于.Net2.0的PetShop 4.0为止&#xff0c;整个设计逐渐变得成熟而优雅&#xff0c;有很多可以借鉴之处。PetShop是一个小型的项目&#xff0c;系统架构与代码…

【设计模式深度剖析】【2】【结构型】【装饰器模式】| 以去咖啡馆买咖啡为例 | 以穿衣服出门类比

&#x1f448;️上一篇:代理模式 | 下一篇:适配器模式&#x1f449;️ 目 录 装饰器模式定义英文原话直译如何理解呢&#xff1f;4个角色类图1. 抽象构件&#xff08;Component&#xff09;角色2. 具体构件&#xff08;Concrete Component&#xff09;角色3. 装饰&#xf…

【调试笔记-20240526-Linux-在 OpenWrt-23.05 发行版上安装 cpolar】

调试笔记-系列文章目录 调试笔记-20240526-Linux-在 OpenWrt-23.05 发行版上安装 cpolar 文章目录 调试笔记-系列文章目录调试笔记-20240526-Linux-在 OpenWrt-23.05 发行版上安装 cpolar 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调试环境调试目标 二、调试步骤…

【找出第 K 大的异或坐标值】python

4层循环暴力超时 class Solution:def kthLargestValue(self, matrix: List[List[int]], k: int) -> int:nums[]for a in range(len(matrix)):for b in range(len(matrix[0])):num0for i in range(a1):for j in range(b1):num^matrix[i][j]nums.append(num)nums.sort()retu…

部门来了个测试开发,听说是00后,上来一顿操作给我看蒙了...

公司新来了个同事&#xff0c;听说大学是学的广告专业&#xff0c;因为喜欢IT行业就找了个培训班&#xff0c;后来在一家小公司实习半年&#xff0c;现在跳槽来我们公司。来了之后把现有项目的性能优化了一遍&#xff0c;服务器缩减一半&#xff0c;性能反而提升4倍&#xff01…

Docker自定义镜像

镜像 镜像包含了应用程序、程序运行的系统函数库、运行配置等文件的文件包。构建自定义镜像就是把上述文件打包的过程。 镜像结构 入口&#xff08;entrypoint&#xff09;&#xff1a;镜像运行入口&#xff0c;一般是程序的启动脚本和参数 层&#xff08;layer&#xff09;&am…

Tensorflow 2.0 安装过程

第一步&#xff1a;进入国内清华软件网站 anaconda | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirroranaconda 使用帮助 | 镜像站使用帮助 | 清华大学开源软件镜像站&#xff0c;致力于为国内和校内用户提供高质量的开源软件镜像、Linux 镜像源服务&…

怎么在Qt Designer设计的界面上显示Matplotlib的绘图?

首先&#xff0c;利用Qt Designer设计界面。 设计好后保存为ui文件。 接着&#xff0c;将ui文件转为py文件。 我喜欢在python中进行转换&#xff0c;因此把转换命令封装为函数&#xff0c;运行一下即可。 import os # pyuic5 -o output_file.py input_file.ui #通过命令把.ui…

OFDM 802.11a的FPGA实现(二十)使用AXI-Stream FIFO进行跨时钟(含代码)

目录 1.前言 2.AXI-Stream FIFO时序 3.AXI-Stream FIFO配置信息 4.时钟控制模块MMCM 5.ModelSim仿真 6.总结 1.前言 至此&#xff0c;通过前面的文章讲解&#xff0c;对于OFDM 802.11a的发射基带的一个完整的PPDU帧的所有处理已经全部完成&#xff0c;其结构如下图所示&…

老外卖27刀每月的教程已经更新

用了两天半的时间&#xff0c;边学习&#xff0c;边整理了一份老外的视频教程&#xff0c;涉及Facebook&#xff0c;YouTube&#xff0c;tiktok等大的流量平台&#xff0c;有案例&#xff0c;有分析&#xff0c;有如何做。 这个教程是老外讲的&#xff0c;没有什么玄乎的塑造价…