当前位置: 首页 > news >正文

Android14 - 绘制系统 - 概览

从Android 12开始,Android绘制系统有结构性变化, 在绘制的生产消费者模式中,新增BLASTBufferQueue,客户端进程自行进行queue的生产和消费,随后通过Transation提交到SurfaceFlinger,如此可以使得各进程将缓存提交到SufrfaceFlinger后合并到同一事务后同步提交,在同一帧生效。实际上,从Android12到Android14整个绘制系统各个环节也都或大或小调整,比如Android13发布了1.3版本的Vulkan, Android14新增了TextureView,等等。本文基于Android14

Android 绘制系统整体架构:

从上到下可以理解为“生产者(Producer)”到“消费者(Consumer)”处理过程

首先WindowManagerService角度每个窗口称为Window一个Window一般一个APP页面或者Status Bar或者Navigation Bar或者WallPaper这些一个个Window。WindowManagerService(WMS)作为服务端对所有客户端窗口添加、层级、布局等进行统一管理WMS每个Window对应一个SurfaceSurface可以理解图像数据缓存持有者以及Canvas持有者Canvas画布提供绘制各种图形能力供开发使用。一个客户端窗口在建立之初,会先向WMS去申请一个SurfaceWMS创建Surface之后,通过binder返回客户端客户端Surface后,会去创建一个BLASTBufferQueue管理图像内存申请每次要使用Surface的Canvas进行绘制前,需要BLASTBufferQueue申请一块内存(dequeue),我们这里称为Buffer,然后生成图像数据写入Buffer。这个向BLASTBufferQueue申请Buffer并写入图像数据的过程,可以认为是“生产”阶段。随后,enqueue这个buffer,将其提交SurfaceFlinger去合成。这个阶段可以理解图像Buffer的“消费”阶段

SurfaceFlinger(SF负责Hardware沟通维护着设备挂载、VSync信号收发、Layer合成工作。WMS每个SurfaceSurfaceFlinger对应生成一个Layer对象客户端将某个Surface上的Buffer提交SurfaceFlinger,实际上就是更新对应LayerBuffer数据SurfaceFlinger调用HWComposer将这些Layer进行合成显示屏幕

AndroidHAL提供称为一个Hardware Composer组件用于隔离具体硬件的交互。Hardware Composer简称HWComposerHWC2(之所以2早期已有一个HWC版本支持软件合成)。SurfaceFlingerLayer数据交给HWComposer,各厂商来负责HWComposer合成接口具体实现合成完毕数据提交屏幕设备缓存(一般称为Frame Buffer)屏幕显示画面

上面过程可以拆解为几部分

  1. Surface创建与管理
  2. 客户端(EndPoint)绘制(Draw)渲染(Render)图像
  3. 第三部分是硬件Composition(合成)工作
  4. Vsync:由硬件产生信号用于同步framebuffer生产消费SurfaceFlingerVsync进行使用管理向上分发APPVsync是不断绘制驱动力,也是图像缓存有序投送到屏幕的重要机制。

现在分别讨论下四部分

  1. Surface的创建与管理

Surface创建过程中有几个角色贯穿其中

PhoneWindow一个Activity对应一个PhoneWindow代表一个应用窗口AMS创建Activity之初PhoneWindow服务端对应window对象(ActivityRecord)已经添加WMS

ViewRootImpl:其主要作用服务端通信承接外部触发绘制调用从而从上往下整个View树进行绘制可以把ViewRootImp理解为View的调度者ViewRootImp逻辑上View Hierarchy最顶层并不是一个真正View持有一个View--DecorViewDecorView才是真正ViewView最上层,包含着Activity的画面内容。在Activity的resume阶段,ViewRootImplrelayout方法会将DecorView添加到WMS中,这样Activity的内容就显示了出来。逻辑上,我们可以把DecorView也理解为一个Window。Activity对应一个PhoneWindow,通过ViewRootImplDecorView在WMS端添加PhoneWindowWindow。

WMS的Session客户端一个进程对应WMS一个Session客户端持有Sessionbinder客户端窗口添加事务客户端都是通过这个SessionWMS通信

WindowContainerWMS管理系统整体Window体系包括位置层级关系通过WindowContainer这个表达一个WindowDisplayContent代表一个屏幕级别WindowDisplayArea代表一块屏幕一块区域比如平板等大屏幕设备可能一块屏幕上同时显示多个应用区域此时就用DisplayArea表达WindowToken简单理解为对应一个客户端Window比如一个应用Activity,这里需要注意的是,Activity的WindowToken是作为ActivityRecord存在的,也就是说ActivityRecord是WindowToken的子类。而Activity具体内容承载者,DecorView对应WindowState上面所有DisplayContentDisplayAreaWindowTokenWindowState都是WindowContainer子类,这些Window在WMS内是以window树的形式组织起来的。事实上DisplayContent下面还有一个层级称为Feature具体层级结构Android12 - WMS之WindowContainer树(DisplayArea)_android windowcontainer-CSDN博客客户端通过Session接口调用添加DecorViewWMS生成一个对应WindowState对象将其作为Activity对应ActivityRecord(也就是WindowToken)window

SurfaceControl:在WMS端,每个WindowContainer对应一个SurfaceControlSurfaceControlWMS端管理Surface具体对象,在WMS端,可以理解一个SurfaceControl就代表一个Surface。SurfaceControl在SurfaceFlinge端对应一个Layer,持有一个layer的句柄handle。所有绘制动作最后提交SurfaceFinger作为Layer去合成。SurfaceControl作用或者Surface作用主要客户端窗口SurfaceFlingerLayer关联起来客户端Add一个DecorView WMS对应创建WindowState同时创建一个SurfaceControl、Layer随后SurfaceControl返回客户端客户端拿到SurfaceControl之后转换成Surface后续绘制就在这个Surface进行

SurfaceComposerClient是一个Binder主要作用是SurfaceControl调用SurfaceFlinger过程中,作为一个通道角色由于SurfaceControlWMS客户端持有所以客户端WMS都可以通过这个通道调用SF比如Layer创建、Graphihc Buffer提交

1. 客户端绘制和渲染

客户端通过Surface中提供的Canvas进行绘制Canvas基于Skia的SKCanvas。Skia(https://skia.org/)是Google管理的开源2D(也可以支持3D)图像库,目前AndroidGoogle ChromeChromeOS、Mozilla FireFoxFireFoxOS使用Skia作为绘制引擎。Skia可以集成OPEN GL和Vulkan进行3D绘制Android Q以后Skia作用加强即使硬件加速场景中绘制也会封装成Skia的GrOpList再提交给GPU。Android 14 Skia目录external/skia

渲染的过程是将画好的图像,进行栅格化(Rasterizer),变成一个个像素,这是一个非常耗时的过程。Android 3以前支持软件渲染Software Render过程如下:

APPViewonDraw阶段使用Canvas绘制通过Skia进行软件栅格化,通过CPU计算,将绘制内容转化成一个个像素信息,随后投送给屏幕进行显示。由于软件渲染效率低,当下软件渲染只是作为兼容方案得以保留,默认使用硬件加速。

硬件加速的流程简单表述如下:

Android硬件加速相关能力封装hwui组件hwui地址platform/frameworks/base/libs/hwui

硬件加速模式下APPonDraw通过Canvas绘制内容最终封装DisplayList一个个GrOp绘制命令然后通过OpenGL或者Vulkan交由GPU进行渲染随后结果投送屏幕显示具体使用OpenGL还是Vulkan可选择早期Android使用OpenGL由于Vulkan支持多线程渲染性能方面优势Android逐渐倾向使用Vulkan进行渲染另外,在哪些维度上进行硬件加速也是可选

整体使用硬件加速情况如果某个View绘制暂时不支持硬件加速,或者在某些位移动画上为了减少渲染成本,可以动过设置ViewlayerType = LAYER_TYPE_SOFTWARE单纯某个特定View使用Software Render。

硬件加速除了利用GPU加速渲染效率 本身计算渲染范围相较软件渲染更加高效软件渲染每次更新一个View局部将使得整个View hierarchy重新渲染硬件加速标注变化部分,所谓damage area,将绘制指令保存DisplayList中,如此大大提高渲染速度

OpenGL ES VS Vulkan

以下OpenGL ESVulkanAndroid发布版本历史

Vulkan作为一个面向更低级别规范、跨平台的API,可以提供更细粒度的内存管理和资源管理

以下VulkanOpenGL ES使用率(from GDC 2023 https://www.youtube.com/watch?v=C7OjI7CpjLw&t=1188s):

对于未来计划OpenGL ES将不会再有功能更新功能只会Vulkan支持因此Vulkan未来Android主推渲染引擎

无论OpenGL还是Vulkan都需要GPU的支持例如常见的车载高端芯片高通8155明确标明支持: OpenGL ES 3.xVulkanhttps://www.qualcomm.com/content/dam/qcomm-martech/dm-assets/documents/qul7413_sa8155_productbrief_r4.pdf

2. HW Composer(HWC2)图像合成

前面提到过,每个window对应一个SF中的Layer合成(Composition)工作就是这些Layer进程合并一个完整屏幕内容提交给硬件屏幕显示出来大概过程如下

页面LayerStatusBarNavigationBar中间APP内容页面,其中可能会有重叠的部分,称为Overlay。Composition工作就是将这三个Layer合并一个画面,计算重叠部分的颜色,提交屏幕显示出来

合成工作发生在渲染后的内容提交给SurfaceFlinger之后。大致流程如下

合成硬件合成的部分软件合成部分。硬件合成除了更高效的同时,可以合成工作GPU解放出来,提高GPU效率,节省能耗。嵌入式设备的SOC中,硬件合成一般独立DPU(Display Processing)完成

比如高通SA8155这款SOC布局如下

其中GPU部分负责渲染,“Dispay Processing”的部分用来处理合成工作

由于硬件合成Layer数量是限制例如高通QCS2290支持4个Layer、AMD有的芯片支持7)以及Layer的PixelFormat(比如支持PIXEL_FORMAT_RGBA_8888不支持YUV)是限制的,因此硬件合成之前如果合成Layer过多或者Format不满足需要使用GPU先进行一轮软件合成合并或转换一些Layer格式

软件合成过程。(Google I/O '18)

4. VSYNC

VSync简介:

首先关注两个重要概念

refresh rate - 60Hz 代表每秒钟屏幕可以更新多少次这一值早期是固定依赖于硬件现代旗舰设备屏幕支持多个刷新率60Hz~165Hz不等而且是可以App定制刷新率

frame rate秒钟GPU可以绘制多少越大越好

VSync一个通用概念LinuxPC移动设备实现

想象一下绘制过程是这样的:GPU绘制数据,将绘制结果投掷给屏幕显示出来

问题是refresh rateFrame Rate并不保证一致频率,也就是是说GPU渲染的时间并不能保证就正好是16ms(60Hz)内完成的。如果只有一块内存(Frame Buffer)用来交换数据,假如Refresh Rate大于Frame Rate由于GPU从上到下写这块内存的在当屏幕来取数据的时候,GPU刚刚在旧基础上一半此时就会出现图片撕裂问题

解决方法是双缓存方案

提供Back Buffer和Frame Buffer两个缓存,屏幕始终Frame Buffer数据显示GPU往Back BufferGPU完全数据写好Back Buffer整个拷贝Frame Buffer这样就能保证屏幕每次完整

此时仍有一个问题如果GPU的Frame Rate大于屏幕Refresh Rate那么屏幕下一可能GPU写完好几就会出现丢帧现象此时就需要VSync

屏幕根据自己的刷新频率,去给上层发送一个VSync信号GPU拿到这个VSync信号绘制这样就能同步屏幕上层绘制节奏

如果屏幕Refresh Rate大于GPUFrame Rate怎么

屏幕将会仍然显示旧帧。比如中间方框两次刷新屏幕仍然显示前一次内容

Android的VSYNC

实际上Android的VSync要复杂得多,主要由SurfaceFlinger负责实现。通过之前的介绍我们知道一帧的绘制过程有APP绘制渲染SurfaceFlinger合成Display硬件读取帧缓存显示图片三个阶段,如果每一个阶段都依赖VSync信号来执行,那可能会出现这种情况:

也就是VSync1时候APP正在绘制渲染SF还没有可以合成东西所以什么不做等到VSync2时候Render1工作已经完成可以合成VSync3时候合成做完了才可以显示屏幕上绘制渲染显示经历3VSync面对这种情况AndroidVSync设计如下

有三种信号

HW_VSYNC_[ID]底层硬件按Refresh Rate的频率发出,一般为60Hz、90Hz、120H等等,随后会通过HWC通知SurfaceFlinger

VSYNC-app:SurfaceFlinger通知给上层应用VSYNC用于控制和驱动应用绘制渲染

VSYNC-sf:通知给SurfaceFlinger自身的,用于合成Layer信号

VSYNC-appVSYNC-sfHW_VSYNC_[ID]并不是同步发送而是有一定延迟,称为相位差HW_VSYNC_[ID]VSYNC-app发出时间差称为app phaseHW_VSYNC_[ID]VSYNC-sf发出时间差称为sf phase这种设计好处是如果同一个VSync周期内,经sf phase后在执行合成时恰好前一步Render完成就可一个周期完成不用非得下一个VSync

另外Android并非直接硬件HW_VSYNC_[ID]信号直接分发应用SurfaceFlinger而是通过先收集HW_VSYNC_[ID]样本,再根据屏幕Refresh Rate、预先配置的相位差等信息,经过计算模拟出来VSYNC-appVSYNC-sf

由于只需要一定的硬件VSync样本便可以模拟出预期VSYNC-appVSYNC-sf因此并不需要一直HW_VSYNC_[ID]信号收到足够样本(在Android 14中为6个)就可以关闭硬件VSync每次合成数据提交屏幕返回一个硬件VSync时间戳(PresentFence),此时SF对比当前模拟VSync硬件VSync是否误差过大如果过大重新打开硬件VSync收集样本重新计算另外每次终端应用主动请求VSync判断前后两次模拟VSync时间差是否超过750ms如果重新请求打开硬件VSyncsystrace硬件VSyncTAGHW_VSYNC_ON_[ID]

参考资料https://source.android.com/docs/core/graphics/implement-vsync

可变刷新率

现代旗舰机屏幕刷新率可变,比如Pixel 5:

可以看到屏幕是支持60Hz90Hz两种刷新率

而且应用层可以在应用级别、窗口级别指定具体刷新率经过应用指定最终刷新率并不一定指定而是经过SurfaceFlinger综合计算得出具体见https://developer.android.com/media/optimize/performance/frame-rate

相关文章:

Android14 - 绘制系统 - 概览

从Android 12开始,Android的绘制系统有结构性变化, 在绘制的生产消费者模式中,新增BLASTBufferQueue,客户端进程自行进行queue的生产和消费,随后通过Transation提交到SurfaceFlinger,如此可以使得各进程将缓…...

Add object from object library 从对象库中添加内置器件

Add object from object library 从对象库中添加内置器件 正文正文 对于 Lumerical,有些时候我们在使用中,可能需要从 Object library 中添加器件,通常我们的做法是手动添加。如下图所示,我们添加一个 Directional Coupler 到我们的工程文件中: 但是这种操作方式不够智能…...

天诚公租房/人才公寓WiFi人脸识别物联网智能门锁解决方案

人才是引领城市高质量发展的重要因素,城市要想吸纳人才的保障便是人才公寓。近年来,全国各地一二三线城市都在大力建设人才公寓,集聚菁英人才,倾力打造人才高地。 一、人才公寓如火如荼建设 2023年底,山东德州提出三年…...

JAVA学习-练习试用Java实现“子集”

问题: 给定一个整数数组 nums,数组中的元素互不相同。返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。可以按任意顺序返回解集。 示例 1: 输入:nums [1,2,3] 输出:[[],[1],[2],[…...

揭秘《庆余年算法番外篇》:范闲如何使用维吉尼亚密码解密二皇子密信

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容,和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣! 推荐:数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航: LeetCode解锁100…...

Java进阶学习笔记11——多态

什么是多态? 多态是在继承/实现情况下一种现象,表现为:对象多态和行为多态。 同一个对象,在不同时刻表现出来的不同形态。 多态的前提: 要有继承/实现关系 要有方法的重写 要有父类引用指向子类对象。 多态的具体代码…...

注意力机制篇 | YOLOv8改进之引入用于目标检测的混合局部通道注意力MLCA

前言:Hello大家好,我是小哥谈。注意力机制是可以帮助神经网络突出重要元素,抑制无关元素。然而,绝大多数通道注意力机制只包含通道特征信息,忽略了空间特征信息,导致模型表示效果或目标检测性能较差,且空间注意模块往往较为复杂。为了在性能和复杂性之间取得平衡,本文提…...

百度生成数据库

问题1: 帮我创建2个表student与score表,要求student表有id,createDate,userName,phone,age,sex,introduce, 要求score表有id,scoreName,result,studentId(student表的id外键)。 要求student表中插入5条学生信息,都要是中文的。 要…...

【SpringBoot】整合百度文字识别

流程图 一、前期准备 1.1 打开百度智能云官网找到管理中心创建应用 全选文字识别 1.2 保存好AppId、API Key和Secret Key 1.3 找到通用场景文字识别,立即使用 1.4 根据自己需要,选择要开通的项目 二、代码编写 以通用文字识别(高精度版&am…...

Java如何设计一个功能

流程说明:实现一组功能的步骤 1,充分了解需求,包括所有的细节,需要知道要做一个什么样的功能。 2,设计实体/表 正向工程:设计实体、映射文件 --> 建表 反向工程:设计表 --> 映射文件、实体 设计实体类型分析步骤: 1)功能模块有几个实体…...

MySQL 字符字段长度设置详解:语法、注意事项和示例

本文将详细介绍在 MySQL 数据库中如何设置字符字段的长度。将介绍字符字段的数据类型、长度限制、语法示例,并提供具体的示例,以正确设置和管理字符字段的长度。 1. MySQL 字符字段长度概述 在 MySQL 中,字符字段是用于存储文本型数据的列。…...

【对角线遍历】python

没啥思路 class Solution:def findDiagonalOrder(self, mat: List[List[int]]) -> List[int]:mlen(mat)nlen(mat[0])ret[]if len(mat)0:return retcount0#mn-1是对角线总数while count<mn-1:#x和y的和刚好是count数#偶数为右上走if count%20:xcount if(count<m)else (…...

温度检测小系统兼继电器模块和小风扇

1.思路&#xff1a; 代码还要封装&#xff01; 延迟1秒&#xff1b;串口初始化&#xff1b;LCD1602显示屏初始化&#xff1b;延迟两秒&#xff1b;ledone不亮&#xff1b; while循环&#xff0c;延迟1秒&#xff0c;DHT模块读取数据&#xff1b;封装接收数据函数&#xff1b;发…...

[数据结构1.0]计数排序

读者老爷好&#xff0c;本鼠鼠最近学了计数排序&#xff0c;浅浅介绍一下&#xff01; 目录 1.统计相同元素出现次数 2.根据统计的结果将序列回填到原来的序列中 3.相对映射计数排序 计数排序又称为鸽巢原理&#xff0c;是对哈希直接定址法的变形应用&#xff0c;是非比较排…...

PostgreSQL入门教程

PostgreSQL是一种开源的关系型数据库管理系统&#xff0c;它具有高度的可靠性、可扩展性和性能。下面是一个简单的PostgreSQL入门教程&#xff0c;帮助你开始使用这个强大的数据库管理系统。 步骤1&#xff1a;安装PostgreSQL 首先&#xff0c;你需要下载并安装PostgreSQL。你…...

【spring】@ControllerAdvice注解学习

ControllerAdvice介绍 ControllerAdvice 是 Spring 框架提供的一个注解&#xff0c;用于定义一个全局的异常处理类或者说是控制器增强类&#xff08;controller advice class&#xff09;。这个特性特别适用于那些你想应用于整个应用程序中多个控制器的共有行为&#xff0c;比…...

【全开源】赛事报名系统源码(Fastadmin+ThinkPHP和Uniapp)

基于FastadminThinkPHP和Uniapp开发的赛事报名系统&#xff0c;包含个人报名和团队报名、成绩查询、成绩证书等。 构建高效便捷的赛事参与平台 一、引言&#xff1a;赛事报名系统的重要性 在举办各类赛事时&#xff0c;一个高效便捷的报名系统对于组织者和参与者来说都至关重…...

杰理-耳机进入关机关闭内内置触摸-节省功耗

杰理-耳机进入关机关闭内内置触摸-节省功耗 if (__this->init 0) {return LP_TOUCH_SOFTOFF_MODE_LEGACY; }if ((__this -> softoff_mode LP_TOUCH_SOFTOFF_MODE_ADVANCE) && (__this->softoff_keep 0)) {lp_touch_key_disable(); } __this->softoff_k…...

Homebrew安装、 Mac上pyenv的安装与使用,复制黏贴搞定,网上教程看得眼花缭乱的来看看,简单明了一步到胃!!

安装 Homebrew /bin/bash -c "$(curl -fsSL https://gitee.com/ineo6/homebrew-install/raw/master/install.sh)"安装pyenv brew install pyenv添加到终端使用的配置文件.zshrc、.bashrc 避免不必要的麻烦两个终端的配置文件都进行添加&#xff0c;文件在当前用户目…...

通过注意力调节实现更好的文本到图像生成对齐

近年来&#xff0c;生成性AI技术在众多领域取得了前所未有的进步。大规模预训练模型的出现激发了各种下游任务中的新应用。这在文本到图像生成领域尤为明显&#xff0c;例如Stable Diffusion、DALL-E 2和Imagen等模型已经显著展示了它们的能力。尽管如此&#xff0c;复杂提示中…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...