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

深入 Flutter 和 Compose 的 PlatformView 实现对比,它们是如何接入平台控件

在上一篇《深入 Flutter 和 Compose 在 UI 渲染刷新时 Diff 实现对比》发布之后,收到了大佬的“催稿”,想了解下 Flutter 和 Compose 在 PlatformView 实现上的对比,恰好过去写过不少 Flutter 上对于 PlatformView 的实现,这次恰好可以用来和 Compose 做个简单对比:

Flutter

其实 Flutter 在 Android 上的 PlatformView 实现过去已经聊过好多次了,Flutter 作为完全脱离平台渲染树的独立 UI 库,它在混合开发的 PlatformView 实现可以说是“历经沧桑” 。

既然前面我们讲过很多次,这里主要就是简单介绍下,方便和 Compose 做个对比,感兴趣的可以去看后面的详细链接。

在 Flutter 上是通过 AndroidView 接入平台控件,目前活跃在 Android 平台的 PlatformView 支持主要有以下三种:

  • Virtual Display (VD)
  • Hybrid Composition (HC)
  • Texture Layer Hybrid Composition (TLHC)

为什么会有这么多不同模式支持?因为主要是随着技术推进和适配场景,PlatformView 的适配需求都在更新,但是新来的又不能完全提前之前的方案,所以就导致实现都并存下来。

VD

VD简单来说就是使用 VirtualDisplay 渲染原生控件到内存,然后利用 id 在 Flutter 界面上占用一个相应大小的位置,最后通过 id 关联到 Flutter Texture 里进行渲染。

问题也很明显,因为控件不会真实存在渲染的位置,可以不严谨理解,它只是内存里 UI 的“镜像”显示,或者说“副屏镜像”,所以此时的点击和对原生控件的操作,其实都是需要由 Flutter 这个 View 进行二次转发到原生再回到 Flutter 。

另外因为控件是渲染在内存里,所以和键盘交互需要通过二级代理处理,容易产生各种键盘输入和交互的异常问题,特别是 WebView 场景。

当然,现在的 VD 已经比初始的时候好很多,并且还在兼容“服役”。

HC

1.2 版本开始支持 HC 模式,这个版本就是直接把原生控件「覆盖」在 FlutterView 上进行堆叠,简单来说就是 HC 模式会直接把原生控件通过 addView 添加到 FlutterView 上 。如果出现 Flutter Widget 需要渲染在 Native Widget 上,就采用新的 FlutterImageView 来承载新图层。

比如在 Layout Inspector,HC 模式可以看出来各种原生布局的边界绘制:

而如下图所示,其中蓝色的文本是原生的 TextView ,红色的文本是 Flutter 的 Text 控件,在中间 Layout Inspector 的 3D 图层下可以清晰看到:

  • 两个蓝色的 TextView 是被添加在 FlutterView 之上,并且把没有背景色的红色 RE 遮挡住了
  • 最顶部有背景色的红色 RE 也是 Flutter 控件,但是因为它需要渲染到 TextView 之上,所以这时候多一个 FlutterImageView ,它用于承载需要显示在 Native 控件之上的纹理,从而达 Flutter 控件“真正”和原生控件混合堆叠的效果。

这里的 FlutterImageView ,其实还有一个作用,就是为了解决动画同步和渲染

当然,这样带来了一个问题,因为此时原生控件是直接渲染,所以需要在原生的平台线程上执行,纯在 Flutter 的 UI 线程就存在线程同步问题,所以在此之前一些场景下会有画面闪烁 bug 。

虽然这个问题最后也通过类似线程同步实现解决,但是也带来一定程度的性能开销,另外在 Android 10 之前还会存在 GPU->CPU->GPU的性能损耗,所以 HC 属于会性能开销较大,又需要原生控件特性的场景

TLHC

3.0 版本开始支持 TLHC 模式,最初的目的是取代上面这两种模式,可惜最终共存下来,该模式下控件虽然在还是布局在该有的位置上,但是其实是通过一个 FrameLayout 代理 onDraw 然后替换掉 child 原生控件的 Canvas 来实现混合绘制。

所以看到此时上图 TextView 里没有了内容,因为 TextView 里的 Canvas 被替换成 Flutter 在内存里创建的 Canvas

其实 TLHC 流程上和 VD 基本一样,简单对比 VirtualDisplayTextureLayer 的实现差异,可以看到主要还是在于原生控件纹理的提取方式上

从上图我们可以得知:

  • 从 VD 到 TLHC, Plugin 的实现是可以无缝切换,因为主要修改的地方在于底层对于纹理的提取和渲染逻辑

  • 以前 Flutter 中会将 AndroidView 需要渲染的内容绘制到 VirtualDisplays ,然后在 VirtualDisplay 对应的内存中,绘制的画面就可以通过其 Surface 获取得到;现在 AndroidView 需要的内容,会通过 View 的 draw 方法被绘制到 SurfaceTexture 里,然后同样通过 TextureId 获取绘制在内存的纹理

从这个简单流程上看,这里面的关键就在于 super.draw(surfaceCanvas); ,给 Android 的 View “模拟” 出来工作环境,然后通过“替换” Canvas 让 View 绘制需要的 Surface 上合成:

那 TLHC 有什么问题?因为它是通过“替换” Canvas 来得到 UI ,但是这种实现天然不支持 SurfaceView等场景,因为 SurfaceView 是自己独立的 Surface 和 Canvas,所以通过 parent 替换 Canvas 的实现并不支持。

所以目前的 PlatformVIew 支持上的结果:

  • 默认会是 TLHC 模式,如果发现接入的 View 是 SurfaceView ,那么就会“降级”使用 VD 来适配
  • 可以通过 initExpensiveAndroidView 接口强行使用 HC

详细链接:

https://blog.csdn.net/ZuoYueLiang/article/details/131800717
https://blog.csdn.net/ZuoYueLiang/article/details/124577097
https://blog.csdn.net/ZuoYueLiang/article/details/124805110

Compose

Compose 的 PlatformView 原理这里可以详细聊聊,这个目前的资料不多,比较有聊的价值。

众所周知,Jetpack Compose 虽然是 Android 平台的全新 UI 开发框架,但是它的 UI 渲染树和「传统 xml View 控件」是“不直接兼容”的,Compose 属于独立的 UI 库,它的 UI 模式更接近 Flutter ,但是 @Composable 函数又不是和 Flutter 一样 return ,在实际工作中,Compose 代码在编译时会给 @Composable 函数添加 Composer 参数 ,而实际的 UI Node Tree 等的创建,都是从“隐藏”的 Composer 开始:

详细可见:https://blog.csdn.net/ZuoYueLiang/article/details/145105060?spm=1001.2014.3001.5501

所以,一旦你需要在 Jetpack Compose 里接入一个原生控件,你就需要用到 PlatformView 的相关实现,PlatformView 本质上就是把「传统 xml View 控件」渲染进 Compose 渲染树里,而在 Compose 在 Android 平台,使用的就是 AndroidView

@Composable
fun CustomView() {var selectedItem by remember { mutableStateOf("Hello from View") }// Adds view to ComposeAndroidView(modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI treefactory = { context ->// Creates viewTextView(context).apply {text = "Hello from View"textSize = 30ftextAlignment = TextView.TEXT_ALIGNMENT_CENTER}}, update = { view ->// View's been inflated or state read in this block has been updated// Add logic here if necessary// As selectedItem is read here, AndroidView will recompose// whenever the state changes// Example of Compose -> View communicationview.text = selectedItem})
}

如上代码所示,通过 AndroidView 我们可以把一个 Android 传统的 TextView 添加到 Compose 里,当然这没什么实际意义,只是作为一个简单例子。

渲染之后,我们可以看到在 Layout Inspector 的 Component tree 里并没有 TextView ,因为它只是被渲染到 Compose 里,但是它其实并不是 “直接” 存在于 Compose 的 LayoutNode ,它只是“依附”在 AndroidView

想知道 AndroidView 的工作原理,我们需要看它的 factory 实现,从源码我们可以看到,它主要是通过 ViewFactoryHolder 创建了一个代理 layoutNode 来“进入” Compose 渲染树:

而 Android 上 ViewFactoryHolder 的实现主要在它基类 AndroidViewHolder ,这里可以看到 AndroidViewHolder 那可是一个“实实在在”的传统 ViewGroup 实现

我们可以假设,我们前面的传统 TextView ,在 AndroidView 内部实际上就是被添加到 AndroidViewHolder 这个 ViewGroup 里 ,而且这里还有一个 Owner ,从命名上也很“关键”。

带着这两个问题,我们继续看,首先我们在 AndroidViewHolder 里可以看到有 layoutNode 的实现,也就是其实这个 Holder ,它既是传统 ViewGroup ,又具备 Compose 里的 layoutNode 实现

通过查看 layoutNode 的实现,我们可以看到:

  • layoutNodeonAttach 到 Compose 布局里的时候,会执行 addAndroidView ,其实这里的 addAndroidView 内部,就是一个 ViewGroupaddView 操作
  • 另外,在 layoutNode 被 onDetach 时执行 removeAndroidView ,内部也就是 ViewGroupremoveViewInLayout
  • 另外还有通过 MeasurePolicy 处理布局,简单说就是将 Compose 的布局状态同步到 AndroidViewHolder 这个 ViewGroup 去布局,给 「传统 XML View」“模拟” 布局环境。

所以我们可以看到,AndroidViewHolder 类似一个“中转站”,它将 Compose UI 的生命周期和测绘布局状态同步到传统 ViewGroup 控件,从而给添加进来的 TextView “模拟” 出布局和绘制环境,大概可以总结:

AndroidViewHolder 类似于 Compose 代理 Node,它 Compose 中的 UI 环境“模拟”到 ViewGroup 中,通过控制 ViewGroup 的绘制与布局来控制我们的「传统 xml View 控件」

那么 AndroidViewHolder 肯定就是从 onAttach 开始进入 Compose 的 LayoutNode 体系工作,这里关键在于 AndroidComposeView 的这个操作:

(owner as? AndroidComposeView)?.addAndroidView(this, layoutNode)

这里又冒出来一个新对象 AndroidComposeView ,它就是我们前面所说的 owner ,那它又是什么?

我们看 AndroidComposeView 的源码,可以看到 AndroidComposeView 同样是一个 ViewGroup ,它的内部主要是有一个 AndroidViewsHandlerViewGroup 在处理 AndroidViewHolder ,比如前面的 addAndroidView 就是将 Holder 添加到 Handler

那到这里就有三个东西:

  • AndroidComposeView
  • AndroidViewsHandler
  • AndroidViewHolder

它们都是传统 ViewGroup 的实现,且关系大概如下所示:

那么到这里,流程上我们应该就清晰了,我们只需要搞清楚 AndroidComposeView 是什么,来自哪里,然后往下,大概就可以理清它的实现。

我们通过 AndroidComposeView 内部有个 root 节点的实现,可以猜测它应该是一个顶层节点,所以我们直接从顶部开始找:

我们从 Activity 开始往下找,经过几个简单调整,就可以在 AbstractComposeView.setContent 找到创建 AndroidComposeView 的地方:

因为 AbstractComposeView 的实现是 ComposeView ,所以可以看到:

AndroidComposeView 是在初始时被 ComposeView 创建并 addView ,然后 Composition 里 UiApplier 的 root 节点就是 AndroidComposeView

所以这就是为什么前面我们那个 owner 为什么是 AndroidComposeView 的来源,然后往下就是 AndroidViewsHandler ,它主要就是持有所有 Holder ,然后根据调用给它的 children 执行各种布局和绘制操作:

所以我们就知道了:

  • 在初始化的时候,Compose 就会创建一个顶层 ViewGroup 节点 AndroidComposeView ,它是一个 root LayoutNode
  • AndroidComposeView 内部的 AndroidViewsHandler 会通过一个 hashMap 去触发和管理 children Holder 的布局和重绘
  • AndroidViewHolder 是一个代理 LayoutNode ,同时它将 Compose UI 的生命周期和测绘布局状态同步到传统 ViewGroup 控件

大概会是下面这样的结构,但是它虽然被 addViewViewGroup 里,但是它并不会直接渲染在 ViewGroup 里 ,而是「被代理渲染」到 LayoutNode 对应的 Scope 里

比如我们接入了两个 SurfaceView 到 Compose ,如果我们打印传统布局结构,大概可以看到这样的一个结果,:

这里举例的 SurfaceView 后面会顺便聊聊 。

最后就是绘制,知道流程后,我们直接看回 AndroidViewHolder 里的 layoutNode 实现,在这里有一个来自 drawBehindcanvas

一般情况下,drawBehind 修饰符可以想任何可组合函数后面绘制内容时,例如:

Text("Hello Compose!",modifier = Modifier.drawBehind {drawRoundRect(Color(0xFFBBAAEE),cornerRadius = CornerRadius(10.dp.toPx()))}.padding(4.dp)
)

而这里的 Canvas 是来自 DrawScopeDrawScope 属于一个针对 Canvas 接口的高级封装,内部 Canvas 的底层支持还是原生平台的 Canvas ,因为 Compose 有多平台支持,而 Android 平台对应的就是 AndroidCanvas 对象,这里是通过 canvas.nativeCanvas 获取到的,就是 android.graphics.Canvas 对象,也就是传入了一个 Android 原生 Canvas

流程上如下图所示,这里的核心其实就是:将 Compose 里 drawBehind 的 Canvas 传递给「传统 XML View」,这样在绘制时用的就是来自 Compose 体系 drawBehind 的 Canvas 链条

所以这里可以看到,在绘制的时候,采用的其实就是通过 AndroidViewHolder 这个 ViewGroup 作为 Parent 来 “替换” 掉作为 child 的传统 View 的 Canvas ,让 View 的内容通过 Compose 的 Canvas 绘制到它所在的 LayoutNode 上

另外, pointerInteropFilter 也会处理手势事件,用户在当前 LayoutNode 交互的手势,会被发送到 AndroidViewHolder 这个 ViewGroup ,从而触发传统 Androd 控件的点击等效果。

最后,在 navigate 切换的时候, AndroidViewHolder 也会相对应的被 add/remove 。

从这角度看,Compose 的 PlatformView 实现和 Flutter 的 TextureLayer 理念很接近,都是通过“替换” Canvas 和“模拟”布局环境来实现 View 接入,但是,它们又有本质不同,这个不同就体现在 SurfaceView

因为 SurfaceView 是有自己独立的 Surface 和 Canvas ,所以它是无法被 Parent 的 Canvas “替换” ,这也是 Flutter 里 TLHC 的问题,但是在 Compose 里,你会发现 SurfaceViewAndroidView 里可以正常工作:

@Composable
fun ContentExample() {Box() {ComposableSurfaceView(Modifier.size(100.dp))Text("Compose", modifier = Modifier.drawBehind {drawRoundRect(color = Color(0x9000FFFF), cornerRadius = CornerRadius(10.dp.toPx()))}.padding(vertical = 30.dp))}
}@Composable
fun ComposableSurfaceView(modifier: Modifier = Modifier) {AndroidView(factory = { context ->SurfaceView(context).apply {layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)holder.addCallback(MySurfaceCallback())//添加回调}}, modifier = modifier)
}class MySurfaceCallback : SurfaceHolder.Callback {private var _canvas: Canvas? = nulloverride fun surfaceCreated(p0: SurfaceHolder) {_canvas = p0.lockCanvas()_canvas?.drawColor(android.graphics.Color.GRAY)//设置背景颜色_canvas?.drawCircle(100f, 100f, 50f, Paint().apply {color = android.graphics.Color.YELLOW})//绘制一个红色的图像p0.unlockCanvasAndPost(_canvas)}
}

可以看到,上面代码的 SurfaceView 灰色的背景和黄色的圆都被渲染出来,另外 TextCompose 文本也正常带着背景色覆盖显示在 SurfaceView 上:

有没有觉得奇怪,为什么 SurfaceViewCanvas 没有被替换,但是 SurfaceView 的内容和层级却又正常渲染在了 Compose UI 树里?

其实道理很简单,虽然 Compose 和「传统 XML View」 是两套 UI 框架,但是 Compose 的本质还是 Android 里面的 View ,也就是它依旧在 View 体系的范畴内

依赖 Android 的 Surface、Window、SurfaceFlinger 体系去渲染。

我们简单回忆下 SurfaceView 是怎么工作的?

  • Android 里控件基本都是以 View 为基类,所有可见 View 对象都会渲染到一个 Surface ,这个 Surface 来自 SurfaceFlinger ,也就是当前 Window 下。

  • 尽管 SurfaceView 继承自类View,但是它有自己独立的 Surface,是直接提交到 SurfaceFlinger

这也是 SurfaceView 会有自己独立 Canvas 的原因,简单说它是一个可以绘制到 Surface 并直接输出到 SurfaceFlinger 的视图。

一般情况下, SurfaceView 在其 Window 层上始终是一个透明的 Rect,类似于**SurfaceView 在其窗口中打了一个洞**, 并且默认情况下,SurfaceView 的 Z 顺序始终低于其附加的 Window 层,也就是 SurfaceView 的 Surface 是在默认 Surface 的下面。

而最终渲染时,SurfaceFlinger 会将 SurfaceView 的图像层和 Window 的图像层叠加在一起

那么回到 Compose,Compose 的底层还是一个传统的 View ,所以它还是依赖 View 的 Surface 和 SurfaceFlinger,也就是:

Compose 和「传统 View」 共用同一个 Window 和 DecorViewAndroidView 作为一个桥接节点,将「传统 View」 “插入” 到 Compose 的布局树中,虽然 SurfaceView 绘制内容是独立的,但在屏幕上是共享一个 WindowSurfaceFlinger 依然会统一管理窗口合成。

如给上方 SurfaceView 的代码加上 setZOrderOnTop(true),就会看到 Compose 的 Text 看不到了,因为此时的 Z 层面发生了变化:

这就是 Compose 和 Flutter 在 AndroidView 上最大的区别:

Flutter 是完全脱离了渲染体系,但是 Compose 还是在 View 体系内,所以 SurfaceView 不会是问题,甚至官方还推出了 SurfaceView 对应的 Compose 封装 AndroidExternalSurfaceScope

只是说,在 「传统 XML View」 体系中,每个 View 会有一个 RenderNode,而 Compose 中“一般”只有 ComposeView 一个 RenderNode,也就是传说的单页面状态,而 Compose 内部最终就是将自己的 LayoutNode 通过 Composer 组合完成后塞到 RenderNode 里面。

最后

可以看到,在 Android 平台上, Flutter 和 Compose 在最终实现思路很接近,大家都叫 AndroidView理念都是“模拟”环境和“替换” Canvas ,但是在 Android 平台上 Compose 有着原生 View 体系的优势,所以它对 SurfaceView 的支持更友好。

相关文章:

深入 Flutter 和 Compose 的 PlatformView 实现对比,它们是如何接入平台控件

在上一篇《深入 Flutter 和 Compose 在 UI 渲染刷新时 Diff 实现对比》发布之后,收到了大佬的“催稿”,想了解下 Flutter 和 Compose 在 PlatformView 实现上的对比,恰好过去写过不少 Flutter 上对于 PlatformView 的实现,这次恰好…...

C# OpenCV机器视觉:红外体温检测

在一个骄阳似火的夏日,全球却被一场突如其来的疫情阴霾笼罩。阿强所在的小镇,平日里熙熙攘攘的街道变得冷冷清清,人们戴着口罩,行色匆匆,眼神中满是对病毒的恐惧。阿强作为镇上小有名气的科技达人,看着这一…...

FCA-FineDataLink认证

FCA-FineDataLink证书 Part.1:判断题 (总分:18分 得分:16) 第1题 判断题 数据同步只支持写入到已存在表,不支持自动建表(得分:2分 满分:2分) 正确答案:B 你的答案&…...

第19篇:python高级编程进阶:使用Flask进行Web开发

第19篇:python高级编程进阶:使用Flask进行Web开发 内容简介 在第18篇文章中,我们介绍了Web开发的基础知识,并使用Flask框架构建了一个简单的Web应用。本篇文章将深入探讨Flask的高级功能,涵盖模板引擎(Ji…...

js截取video视频某一帧为图片

1.代码如下 <template><div class"box"><div class"video-box"><video controls ref"videoRef" preload"true"src"https://qt-minio.ictshop.com.cn:9000/resource-management/2025/01/08/7b96ac9d957c45a…...

[云讷科技]Kerloud Falcon四旋翼飞车虚拟仿真空间发布

虚拟仿真环境作为一个独立的专有软件包提供给我们的客户&#xff0c;用于帮助用户在实际测试之前验证自身的代码&#xff0c;并通过在仿真引擎中添加新的场景来探索新的飞行驾驶功能。 环境要求 由于环境依赖关系&#xff0c;虚拟仿真只能运行在装有Ubuntu 18.04的Intel-64位…...

Jetson nano 安装 PCL 指南

本指南帮助 ARM64 架构的 Jetson Nano 安装 PCL&#xff08;点云库&#xff09;。 安装步骤 第一步&#xff1a;安装依赖 在终端中运行以下命令&#xff0c;安装 PCL 所需的依赖&#xff1a; sudo apt-get update sudo apt-get install git build-essential linux-libc-dev s…...

go-zero框架基本配置和错误码封装

文章目录 加载配置信息配置 env加载.env文件配置servicecontext 查询数据生成model文件执行查询操作 错误码封装配置拦截器错误码封装 接上一篇&#xff1a;《go-zero框架快速入门》 加载配置信息 配置 env 在项目根目录下新增 .env 文件&#xff0c;可以配置当前读取哪个环…...

Android中Service在新进程中的启动流程2

目录 1、Service在客户端的启动入口 2、Service启动在AMS的处理 3、Service在新进程中的启动 4、Service与AMS的关系再续 上一篇文章中我们了解了Service在新进程中启动的大致流程&#xff0c;同时认识了与客户端进程交互的接口IApplicationThread以及与AMS交互的接口IActi…...

论文速读|Matrix-SSL:Matrix Information Theory for Self-Supervised Learning.ICML24

论文地址&#xff1a;Matrix Information Theory for Self-Supervised Learning 代码地址&#xff1a;https://github.com/yifanzhang-pro/matrix-ssl bib引用&#xff1a; article{zhang2023matrix,title{Matrix Information Theory for Self-Supervised Learning},author{Zh…...

ubunut22.04安装docker(基于阿里云 Docker 镜像源安装 Docker)

安装 更新包管理器&#xff1a; sudo apt update 安装 Docker 的依赖包 sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release添加阿里云 Docker 镜像源 GPG 密钥&#xff1a; curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gp…...

k8s namespace绑定节点

k8s namespace绑定节点 1. apiserver 启用准入控制 PodNodeSelector2. namespace 添加注解 scheduler.alpha.kubernetes.io/node-selector3. label node 1. apiserver 启用准入控制 PodNodeSelector vim /etc/kubernetes/manifests/kube-apiserver.yaml spec:containers:- co…...

【ElementPlus】在Vue3中实现表格组件封装

预览 搜索筛选组件 <template><div><el-formref"formView":model"formData"label-width"auto"label-position"right":label-col-style"{ min-width: 100px }":inline"true"><el-form-item …...

cursor重构谷粒商城04——vagrant技术快速部署虚拟机

前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶到中高阶程序员。 本项目将基于谷粒商城…...

26、正则表达式

目录 一. 匹配字符 .&#xff1a;匹配除换行符外的任意单个字符。 二. 位置锚点 ^&#xff1a;匹配输入字符串的开始位置。 $&#xff1a;匹配输入字符串的结束位置。 \b&#xff1a;匹配单词边界。 \B&#xff1a;匹配非单词边界。 三. 重复限定符 *&#xff1a;匹配…...

SpringBoot使用MockMVC通过http请求controller控制器调用测试

说明 在Spring Boot中编写测试控制器调用是一个常见的需求,通常使用Spring的测试框架来完成。Spring Boot提供了多种方式来测试控制器,包括使用MockMvc进行模拟HTTP请求和响应的测试。 基本示例 1. 创建Spring Boot项目 首先,确保你已经创建了一个Spring Boot项目。如果…...

【Unity3D】Unity混淆工具Obfuscator使用

目录 一、导入工具 二、各种混淆形式介绍 2.1 程序集混淆 2.2 命名空间混淆 2.3 类混淆 2.4 函数混淆 2.5 参数混淆 2.6 字段混淆 2.7 属性混淆 2.8 事件混淆 三、安全混淆 四、兼容性处理 4.1 动画方法兼容 4.2 GUI方法兼容 4.3 协程方法兼容 五、选项 5.1 调…...

C语言语法基础学习—动态分配空间(new和malloc的用法及区别)

前言 在 C 语言中&#xff0c;动态内存分配主要是通过 malloc() 和 free() 函数来完成的。而在 C 中是使用new和delete关键字&#xff0c;来动态分配内存。 虽然 C 语言没有 new&#xff0c;但 malloc() 和 new 在内存分配上的作用是相似的。下面我们详细解释 malloc() 和 ne…...

QT:控件属性及常用控件(3)-----输入类控件(正则表达式)

输入类控件既可以进行显示&#xff0c;也能让用户输入一些内容&#xff01; 文章目录 1.Line Edit1.1 用户输入个人信息1.2 基于正则表达式的文本限制1.3 验证两次输入的密码是否一致1.4 让输入的密码可以被查看 2.Text Edit2.1 输入和显示同步2.1 其他信号出发情况 3.ComboBox…...

Hive SQL 执行计划解析

Hive SQL 执行计划解析 一、 explain用法 1. SQL 查询 EXPLAIN SELECT SUM(view_dsp) AS view_sum FROM ads.table_a WHERE p_day 2025-01-06;2. 执行计划 STAGE DEPENDENCIES:Stage-1 is a root stageStage-0 depends on stages: Stage-1STAGE PLANS:Stage: Stage-1Map …...

热更新杂乱记

热更新主要有一个文件的MD5值的比对过程&#xff0c;期间遇到2个问题&#xff0c;解决起来花费了一点时间 1. png 和 plist 生成zip的时候再生成MD5值会发生变动。 这个问题解决起来有2种方案&#xff1a; &#xff08;1&#xff09;.第一个方案是将 png和plist的文件时间改…...

博客搭建 — GitHub Pages 部署

关于 GitHub Pages GitHub Pages 是一项静态站点托管服务&#xff0c;它直接从 GitHub 上的仓库获取 HTML、CSS 和 JavaScript 文件&#xff0c;通过构建过程运行文件&#xff0c;然后发布网站。 本文最终效果是搭建出一个域名为 https://<user>.github.io 的网站 创建…...

翻译:How do I reset my FPGA?

文章目录 背景翻译&#xff1a;How do I reset my FPGA?1、Understanding the flip-flop reset behavior2、Reset methodology3、Use appropriate resets to maximize utilization4、Many options5、About the author 背景 在写博客《复位信号的同步与释放&#xff08;同步复…...

Linux 进程环境变量:深入理解与实践指南

&#x1f31f; 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。&#x1f31f; &#x1f6a9;用通俗易懂且不失专业性的文字&#xff0c;讲解计算机领域那些看似枯燥的知识点&#x1f6a9; 在 Linux 系统里…...

Linux探秘坊-------5.git

1.git介绍 1.版本控制器 为了能够更⽅便我们管理这些不同版本的⽂件&#xff0c;便有了版本控制器。所谓的版本控制器&#xff0c;就是能让你了解到⼀个⽂件的历史&#xff0c;以及它的发展过程的系统。通俗的讲就是⼀个可以记录⼯程的每⼀次改动和版本迭代的⼀个管理系统&am…...

Linux中的几个基本指令(二)

文章目录 1、cp指令例一&#xff1a;例二&#xff1a;例三&#xff1a;例四&#xff1a;例五&#xff1a; 2、mv 指令例一&#xff1a;例二&#xff1a; 3、cat指令例一&#xff1a; 4、tac指令5、which指令6、date指令时间戳&#xff1a;7、zip指令 今天我们继续学习Linux下的…...

Java入门笔记(1)

引言 在计算机编程的广袤宇宙中&#xff0c;Java无疑是一颗格外耀眼的恒星。那么&#xff0c;Java究竟是什么呢&#xff1f; Java是美国Sun公司&#xff08;Stanford University Network&#xff09;在1995年推出的一门计算机高级编程语言。曾经辉煌的Sun公司在2009年被Oracle&…...

设计模式的艺术-开闭原则

原则使用频率图&#xff08;仅供参考&#xff09; 1.如何理解开闭原则 简单来说&#xff0c;开闭原则指的是 “对扩展开放&#xff0c;对修改关闭”。 当软件系统需要增加新的功能时&#xff0c;应该通过扩展现有代码的方式来实现&#xff0c;而不是去修改已有的代码。 例如我…...

【C语言系列】深入理解指针(3)

深入理解指针&#xff08;3&#xff09; 一、字符指针变量二、数组指针变量2.1数组指针变量是什么&#xff1f;2.2数组指针变量怎么初始化&#xff1f; 三、二维数组传参的本质四、函数指针变量4.1函数指针变量的创建4.2函数指针变量的使用4.3两段有趣的代码4.4 typedef关键字 …...

three.js+WebGL踩坑经验合集:写在前面的话

笔者从2023年初开始参与一个基于three.js引擎的web项目的开发工作。本打算2024年春节就把期间踩过的坑写到博客上分享给大家&#xff0c;然而哪怕本专栏的各种构思和内容已经在笔者的脑海里翻滚了一年&#xff0c;得了严重拖延症患者的我还是一直拖到了现在&#xff0c;实在惭愧…...