HarmonyOS开发实战( Beta5版)高负载组件的渲染实践规范

news2024/11/16 4:29:02

简介

在应用开发中,有的页面需要在列表中加载大量的数据,就会导致组件数量较多或者嵌套层级较深,从而引起组件负载加重,绘制耗时增长。虽然可以通过组件复用避免组件重复创建,但是如果每个列表项中包含的组件较多,在转场或者列表滑动的时候列表项就会一次性加载大量的数据,可能引起卡顿掉帧等性能问题。

转场场景

由于业务需求,从当前页面进入一个新页面时,会有转场动画播放,并且在动画首帧中加载新页面所需要的数据。如果数据量较多,那么动画首帧的响应时延就会变长,导致后面的动画帧延迟播放,产生卡顿的情况。

动画

解决思路

既然转场时一次性加载大量的数据会导致卡顿情况,那么将数据拆分成多份并分批次进行加载就是一种解决思路。ArkTS中提供了DisplaySync(可变帧率),可以设置帧回调监听,让开发者在不同的帧中进行一些操作,这样就可以将本来在一帧中加载的数据分到多帧中加载,减少动画首帧的响应时间,降低完成时延。

常规代码

在自定义列表组件中一次性加载全部数据,可参考组件堆叠场景中的具体实现。

// CommonAppDevelopment/feature/componentstack/src/main/ets/view/ProductList.ets
@Component
export struct ProductList {
  private productData: ProductDataSource = new ProductDataSource();

  aboutToAppear(): void {
    this.productData.pushData(PRODUCT_DATA)
  }

  build() {
    WaterFlow() {
      LazyForEach(this.productData, (item: ProductDataModel) => {
        FlowItem() {
          ItemView({ item: item })
        }
      }, (item: ProductDataModel) => item.id.toString())
    }
    ...
  }
}

这段代码里,在aboutToAppear()接口中,将数据放入productData中,并通过瀑布流加载。编译运行后,可以通过Trace图看到,转场动画的首帧(235970)耗时21ms左右,这是因为在点击进入页面时将6条数据全部放入瀑布流,在235970帧中需要计算6个子组件的尺寸,导致了响应时间增长。如果数据量更大,那么这个时间会变得更长,动画的卡顿效果就会更加明显。

image-20240717183013984

优化代码

在aboutToAppear()接口中添加DisplaySync的帧回调,并将数据拆分进行加载。

@Component
export struct ProductList {
  private productData: ProductDataSource = new ProductDataSource();
  private displaySync: displaySync.DisplaySync | undefined = undefined;
  private frame: number = 1;

  aboutToAppear(): void {
    // 创建DisplaySync对象
    this.displaySync = displaySync.create();
    // 设置期望帧率
    const range: ExpectedFrameRateRange = {
      expected: 120,
      min: 60,
      max: 120
    };
    this.displaySync.setExpectedFrameRateRange(range);
    // 添加帧回调
    this.displaySync.on("frame", () => {
      if (this.frame === 1) {
        hiTraceMeter.startTrace('firstFrame', 1);
        this.productData.pushData(PRODUCT_DATA.slice(0, 2))
        this.frame += 1;
        hiTraceMeter.finishTrace('firstFrame', 1);
      } else if (this.frame === 2) {
        hiTraceMeter.startTrace('secondFrame', 2);
        this.productData.pushData(PRODUCT_DATA.slice(2, PRODUCT_DATA.length));
        hiTraceMeter.finishTrace('secondFrame', 2);
        this.frame += 1;
        this.displaySync?.stop();
      }
    });
    // 开启帧回调监听
    this.displaySync.start();
  }

  aboutToDisappear(): void {
    // 页面销毁时需要停止帧回调
    this.displaySync?.stop();
  }

  build() {
    // TODO: 知识点:瀑布流容器,由“行”和“列”分割的单元格所组成,通过容器自身的排列规则,将不同大小的“项目”自上而下,如瀑布般紧密布局。
    WaterFlow() {
      LazyForEach(this.productData, (item: ProductDataModel) => {
        FlowItem() {
          ItemView({ item: item })
        }
      }, (item: ProductDataModel) => item.id.toString())
    }
    .nestedScroll({
      scrollForward: NestedScrollMode.PARENT_FIRST,
      scrollBackward: NestedScrollMode.SELF_FIRST
    })
    .columnsTemplate("1fr 1fr")
    .columnsGap(COLUMNSGAP)
    .rowsGap(ROWSGAP)
    .padding({ bottom: $r("app.integer.component_stack_water_flow_padding_bottom") })
  }
}

在这段代码中,aboutToAppear()接口中并没有一次性加载全部数据,而是将数据拆分,在帧回调中分成2次进行加载。编译运行后,通过Trace图可以看到,动画首帧(232011)的耗时是12ms。相较于优化前的代码,不再是首帧占据大量的时间,而是将耗时分摊到了后面的动画帧中。在点击进入页面时,只放入了2条数据,所以232011帧的Measure[WaterFlow]只需要计算2个子组件的尺寸,并将剩余的4条数据放入productData,让后面的一帧(232013)计算剩余子组件的尺寸。当数据量更大时,可以将数据进行更多次拆分,将不会直接出现在屏幕上的数据放到第二帧或者第三帧中进行加载,降低首帧的响应时延,进而减少转场动画的卡顿现象。

image-20240717183924458

滑动场景

在日历应用中,需要在一个List里面加载每个月的全部天数,包括公历和农历日期,这样在一个Item中就会有最少58条数据加载,也就相当于需要58个组件。当列表滑动的时候,通过组件复用的aboutToReuse()接口设置新的数据,就会导致可能有58个组件一起刷新,可能会引起掉帧卡顿现象。

image-20240507183126622

解决思路

由于一次性加载大量数据、刷新大量组件会导致卡顿丢帧,那么减少一次性加载的数据量就是一种解决方法。但是由于业务需求,需要加载的数据总量和绘制的组件数量是不能减少的,那么只能想办法将数据进行拆分,将和数据相关的组件分成多次进行绘制。ArkTS中提供了DisplaySync(可变帧率),支持开发者设置回调监听,可以在回调里做一些数据的处理,在每一帧中加载少量的数据,减少卡顿或者滑动动画的掉帧现象。

优化示例

常规代码

通常情况下,会在aboutToReuse()中设置新的数据,并一次性绘制所有的组件。

@Entry
@Component
struct Direct {
  ...
  // 初始化日历中一年的数据
  initCalenderData() {
    ...
  }

  aboutToAppear() {
	...
    this.initCalenderData();
  }

  build() {
    Column() {
      ...
      List() {
        LazyForEach(this.contentData, (monthItem: Month) => {
          // 每个月的日期
          ListItem() {
            ItemView({
              monthItem: monthItem,
              currentMonth: this.currentMonth,
              currentDay: this.currentDay
            })
            // 根据每月的天数设置复用ID,组件复用时会选择相同ID的组件进行复用
            .reuseId("reuse_id_" + monthItem.days.length.toString())
          }
        })
      }
      ...
  }
}
@Reusable
@Component
struct ItemView {
  @State monthItem: Month = { month: '', num: 0, days: [], lunarDays: [] };
  ...

  aboutToReuse(params: Record<string, Object>): void {
    hiTraceMeter.startTrace("reuse_" + (params.monthItem as Month).month, 1);
    this.monthItem = params.monthItem as Month;
    hiTraceMeter.finishTrace("reuse_" + (params.monthItem as Month).month, 1);
  }

  build() {
    Flex({ wrap: FlexWrap.Wrap }) {
      ...
      // 日期信息
      ForEach(this.monthItem.days, (day: number, index: number) => {
        ...
      }, (index: number): string => index.toString())
    }
    ...
  }
}

在上面的代码中,通过组件复用,在ItemView的aboutToReuse()接口中,将一个月的数据直接设置到状态变量monthItem中,这样下面的Flex就会收到状态变量变更的消息通知,从而刷新组件中的数据。编译运行后,进入日历页面,通过SmartPerf Host工具,开始抓取Trace,然后滑动列表到最底端,结束Trace的抓取,通过SmartPerf Host对抓取的Trace文件进行分析,选中标签和相关数据区域,可以得到图1。图中三个Actual Timeline标签分别代表应用和RenderService每帧的总耗时、应用每帧的绘制时间和RenderService层每帧的绘制时间,render_service标签表示RenderService层每帧中的绘制操作,example.display标签是应用的bundlename,表示应用在每一帧中的操作,包括创建组件、加载数据等。

图1

image-20240507183017893

通过图中信息可以看到,滑动期间的帧率是113帧,按照手机120帧来计算,滑动期间掉帧率约为5.8%。放大图1后可以看到,应用每次加载新数据时(图2中橙色部分)RenderService层都会有一帧出现异常情况(图2中黄色部分)。此处对于图中颜色区域的解释,可参考SmartPerf Host工具。

图2

image-20240507183126622

将其中一部分继续放大后可以得到图3。选中Actual Timeline(render_service)标签中的146272后,可以通过箭头看到它所关联到的位置是Actual Timeline(example.display)标签中的209136和209137,即RenderService层出现的异常情况是由应用层中前面两帧里面的操作引起的。结合代码和箭头2的标签可以看到,在209135中调用了aboutToReuse接口,此时系统开始了组件复用的绘制操作。通过代码可以看到,在aboutToReuse接口将一个月的所有数据全部放入了当前被复用的组件中,并更新了所有的用于显示日期的Text组件中的数据(箭头3,diffIndexArray.lenght:35,表示有35个不同的元素),这就导致209136需要计算35个子组件的尺寸(箭头1),从而引起146272的绘制时间延长。在列表数据量较少时,其实并不会引起掉帧现象,因为每次延长帧的时间都很短,对帧率的影响较小,但是在列表数据较多时,就会因为延长帧过多,发生掉帧现象。

图3

image-20240507184557969

优化代码

通过DisplaySync中的帧回调方法,将数据拆分到每一帧中进行加载和绘制。此处只需要修改自定义子组件ItemView中加载数据的方式,所以与常规代码中相同的部分进行了省略。

首先,需要在ItemView中第一次使用时创建DisplaySync对象,设置期望帧率,添加帧回调的监听,然后进行启动。

@Reusable
@Component
struct ItemView {
  ...
  aboutToAppear(): void {
    // 创建DisplaySync对象
    this.displaySync = displaySync.create();
    // 初始化期望帧率
    let range: ExpectedFrameRateRange = {
      expected: 120,
      min: 60,
      max: 120
    };
    // 设置期望帧率
    this.displaySync.setExpectedFrameRateRange(range);
    // 设置帧回调监听
    this.displaySync.on("frame", () => {
      ...
    });
    // 开启监听帧回调
    this.displaySync.start();
    ...  
  }
  ...
}

然后,在监听中添加更新数据的代码。这里将每个月的数据更新拆分开来,第一步用来更新月份数据和计算总的执行步骤,最后一步将计数数据初始化,其余需要执行步骤的多少根据每次加载数据量会有所改变。

...
private temp: Month[] = [];
...
this.displaySync.on("frame", () => {
  // 数组中有数据时才开始执行
  if (this.temp.length > 0) {
    if (this.step === 0) {
      // 第一步:放入月份数据,并计算最多需要几帧完成数据操作
      hiTraceMeter.startTrace("reuse_" + this.step, 1);
      this.month = this.temp[0].month;
      this.monthNumber = this.temp[0].num;
      this.year = this.temp[0].year;
      this.maxStep = this.maxStep + Math.ceil(this.temp[0].days.length / this.MAX_EVERY_FRAME);
      hiTraceMeter.finishTrace("reuse_" + this.step, 1);
      this.step += 1;
    } else if (this.step === this.maxStep - 1) {
      // 最后一步:初始化部分计数数据
      this.temp.shift();
      this.step = 0;
      this.maxStep = 2;
    } else {
      hiTraceMeter.startTrace("reuse_" + this.step, 1);
      // 计算从所有数据中取值时的开始索引  
      let start: number = this.MAX_EVERY_FRAME * (this.step - 1);
      // 计算从所有数据中取值时的结束索引  
      let end: number = (this.MAX_EVERY_FRAME * this.step) > this.temp[0].days.length ? 
            this.temp[0].days.length : this.MAX_EVERY_FRAME * this.step;
      // 更新日期数据  
      for (let i = start; i < end; i++) {
        this.days[i] = this.temp[0].days[i];
        this.lunarDays[i] = this.temp[0].lunarDays[i];
      }
      hiTraceMeter.finishTrace("reuse_" + this.step, 1);
      this.step += 1;
    }
  }
});
...

最后,在aboutToReuse接口中将数据放入数组中,用于帧回调中开始执行数据更新。

aboutToReuse(params: Record<string, Object>): void {
  hiTraceMeter.startTrace("reuse_" + (params.monthItem as Month).month, 1);
  this.temp.push(params.monthItem as Month);
  hiTraceMeter.finishTrace("reuse_" + (params.monthItem as Month).month, 1);
}

编译运行后,使用相同的方法,查看优化后的Trace信息,如图4所示。

图4

image-20240507190154885

从图4中可以看到,通过代码优化后,帧率是正常的120帧了。然后将图4中的Trace结果放大后可以看到图5,RenderService层出现的延长帧(Actual Timeline(render_service)标签中的黄色部分)明显减少了,已经不是优化前每次加载数据都会出现的情况了。

图5

image-20240507190741305

下面将图5中的信息继续放大一些,看一下现在每一帧里都做了什么操作,如图6所示。在211618中,开始调用aboutToReuse接口,由于只是将数据放入一个数组中,并没有更新复用组件中的数据,所以这一帧并没有发生延长现象。在211619中开始逐步更新复用组件中的数据,但是由于前一帧(211618)中并没有更新当前复用组件中的数据,所以在211619中并不需要绘制组件,所以此帧耗时依旧很短。结合代码可以看到,在211620中放入了5天的日期数据,由于前一帧(211619)只是设置了2条数据,并且只有1条会更新组件(this.month = this.temp[0].month会更新显示月份的Text),所以这一帧的绘制时间也不会超时。

图6

image-20240507195613210

继续看后面的Trace信息,如图7所示。和前一帧(211621)一样,此帧中更新了5天的日期数据,并且会重新测量上一帧(211621)中更新数据的5个Text组件尺寸(箭头1),而其余的组件由于数据并没有变动,所以测量被略过了(箭头2)。后面的帧是类似的,每次只会放入5天的数据,并且更新上一帧中设置的数据所关联的Text组件。由于每次更新的组件数量较少,每帧基本上都能在规定的时间内(1秒120帧,即8ms一帧)绘制完成,所以延长帧就会较少。这样不论列表中数据多还是少,都不会引起掉帧现象的发生。

图7

image-20240507200236522

不建议锁定最高帧率运行

不建议将ExpectedFrameRateRange中的expected、min、max都设置为120,否则会干扰系统的可变帧率机制运行,产生不必要的负载,进而影响到整机的性能和功耗。

反例

let sync = displaySync.create();
sync.setExpectedFrameRateRange({
  expected: 120,
  min: 120,
  max: 120
})

正例

let sync = displaySync.create();
sync.setExpectedFrameRateRange({
  expected: 60,
  min: 0,
  max: 60
})

主要原因有以下三点:

  1. ExpectedFrameRateRange中关键参数是expected(期望帧率),系统会优先按照expected设置的帧率执行。当系统难以满足expected帧率诉求时,会在min和max之间选一个更合适的帧率给到应用。
  2. 如果应用锁定120HZ,系统会优先满足应用的显式设置,按照120帧运行。此时手机功耗会显著增加,长时间运行会引起手机过热等严重影响用户体验的问题。同时也由于不必要的高帧率,会额外占据更多的算力,可能导致其他场景的响应受到不必要的延迟。
  3. 如果系统持续按照120HZ运行,从某种意义上来说此时系统的可变帧率能力已失效,这显然与可变帧率的设计原则不相符。

总结

通过上面的示例代码和优化过程,可以看到在列表中使用组件复用时,一次性全部加载时可能会引起掉帧。虽然在数据量较少时,单帧绘制的延长并不会引起掉帧,但是数据量变多后,这种延长帧的影响就会比较明显。合理进行数据拆分后,可以有效减少延长帧的发生,从而减少掉帧引起的性能问题。

FAQ

Q:在ItemView中,为什么要给ForEach设置第三个参数?

A:在组件复用中,如果有用到ForEach,必须设置第三个参数,即给每个数据设置一个key,否则ForEach中添加的组件不会被复用,而是会全部重新创建。

Q:对List中每一个ListItem的子组件都设置一个DisplaySync的帧回调监听,会不会引起性能问题?

A:并不会,通过示例中的Trace图可以看到,除了正在被复用的ItemView的DisplaySync的回调监听外,其余的监听耗时非常短,对性能的影响可以忽略不计。如图8所示。

图8

image-20240507194742115

Q:为什么抓取到的Trace中没有示例中那么多的标签?

A:需要通过hdc shell命令开启标签

hdc shell
param set persist.ace.debug.enabled 1
param set persist.ace.trace.enabled 1
param set persist.ace.trace.layout.enabled true
param set const.security.developermode.state true
param set persist.ace.trace.build.enabled 1

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

GitCode - 全球开发者的开源社区,开源代码托管平台  希望这一份鸿蒙学习文档能够给大家带来帮助~


鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习教程+学习PDF文档

HarmonyOS Next 最新全套视频教程

  纯血版鸿蒙全套学习文档(面试、文档、全套视频等)       

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

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

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

相关文章

C/C++:函数指针

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 引言函数指针的概念函数指针的实现C语言实现C实现 函数指针的应用 引言 我们之前学过各种各样指针&#xff0c;今天我们来讨论一下函数指针 我们先分析C和C不同定义函数指针的方式&#xff0c;然后进…

uniapp去除顶部标题栏

相信很多同学和我一样&#xff0c;刚学uniapp的时候想去除自带的这个标题栏不知道如何去除&#x1f92a; 其实很简单&#xff0c;只需两个步骤即可彻底除掉&#xff0c;首先找到项目文件夹下的pages.json路由文件点开&#xff0c;在这个文件里可以看到你创建的所有页面&#x…

git修改提交名字

大家在使用git的时候&#xff0c;有的时候可能不是使用自己的账号&#xff0c;或者说账号的信息不符合自己的预期&#xff0c;具体表现在什么地方呢&#xff1f;在提交代码的时候&#xff0c;名字不是自己的&#xff0c;或者是名字不是自己想要的。 下面就是如何查看和修改。 …

视频智能分析平台LntonAIServer视频质量诊断功能花屏、抖动、遮挡等检测

LntonAIServer新增了视频质量诊断功能&#xff0c;该功能专注于提升视频监控系统的稳定性和可用性&#xff0c;主要通过自动化检测来识别视频流中常见的质量问题&#xff0c;比如花屏、抖动、遮挡等问题。这些问题是影响视频监控效果的主要因素之一&#xff0c;而自动化的检测能…

K8S介绍及Kubeadm方式安装K8S(前期工作)

1.K8S介绍 1.Kubernetes简介 Kubernetes 缩写&#xff1a;K8S&#xff0c;k 和 s 之间有八个字符&#xff0c;所以因此得名。 Kubernetes 由 google 的 Brog 系统作为原型&#xff0c;后经 Go 语言延用 Brog 的思路重写&#xff0c;并捐献给 CNCF 基金会开源。 Kubernetes …

ts函数的参数加一个_是什么意思

先说一下总结&#xff0c;在TypeScript&#xff08;TS&#xff09;和Vue 3项目中&#xff0c;给函数的参数加一个下划线&#xff08;_&#xff09;前缀通常是一种约定或习惯&#xff0c;用来表示该参数在当前函数体内是故意未使用的&#xff0c;需要注意的是&#xff0c;这种做…

电脑开机出现no operation system found错误原因分析及解决方法

最近有网友问我电脑一启动提示&#xff1a;no operation system found&#xff0c;这个提示意思是未找到操作系统。并且出现bios能认别硬盘&#xff0c;快捷启动时找不到硬盘&#xff0c;出现该提示的原因有很多&#xff0c;下面我们来详细分析一下开机出现no operation system…

我的世界桃花源官网源码 游戏官网

我的世界桃花源官网源码 游戏官网 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89714345 更多资源下载&#xff1a;关注我。

【python因果推断库6】使用 pymc 模型的工具变量建模 (IV)1

目录 使用 pymc 模型的工具变量建模 (IV) 使用 pymc 模型的工具变量建模 (IV) 这份笔记展示了一个使用工具变量模型&#xff08;Instrumental Variable, IV&#xff09;的例子。我们将会遵循 Acemoglu, Johnson 和 Robinson (2001) 的一个案例研究&#xff0c;该研究尝试解开…

MemLong: 长文本的新记忆大师,可将上下文长度从4k提升到80k!

这篇文章介绍了一个名为MemLong的模型&#xff0c;它通过使用外部检索器来增强长文本建模的能力。MemLong结合了一个不可微的检索-记忆模块和一个部分可训练的解码器-仅语言模型&#xff0c;并引入了一种细粒度、可控的检索注意力机制&#xff0c;利用语义级别的相关块。在多个…

SpringBoot后端快速搭建

SpringBoot 开发环境构建 首先创建一个maven项目 在pom.xml文件中添加以下依赖 <!-- 依赖的父级工程 --> < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > &l…

本地Gitblit使用

首先创建一个本地的gitblit的服务&#xff0c;创建流程如下&#xff1a; 【GitBlit】Windows搭建Git服务器详细教程_搭建gitblit服务-CSDN博客 GitBlit的使用教程-CSDN博客 创建好一个仓库后&#xff0c;分配好用户权限&#xff0c;再将项目拉下来&#xff0c;这里是再visua…

第二证券:什么是券商理财,券商理财有风险吗?

券商理财是指证券公司发行的理财产品&#xff0c;证券公司简称为券商&#xff0c;证券公司集结出资者资产主张建立的资产处理升值类产品便是券商理财产品。 券商理财产品中主要有质押式报价回购事务、收益凭证、券商资产处理计划三种。 1、质押式报价回购事务 是证券公司将契…

云计算和传统IT相比,有哪些优势?

云计算相比于传统的IT基础设施&#xff0c;具有以下一些显著的优势&#xff1a; 成本效益&#xff1a; 云计算通常采用按需付费模式&#xff0c;用户只需为实际使用的资源支付费用&#xff0c;避免了高昂的前期硬件投资和维护成本。 弹性计费方式使得企业可以根据业务需求灵活调…

如何做好API安全

在数字化时代&#xff0c;API&#xff08;应用程序接口&#xff09;已成为企业间、应用程序间乃至整个数字生态系统中数据交换与功能集成的核心&#xff0c;可 帮助跨多个设备互连多个应用程序或软件系统&#xff0c;定义它们可以发出的调用或请求的种类、调用的方式、应使用的…

C#复习封装_运算符重载

知识点一 基本概念 知识点二 基本语法 知识点三 实例 知识点四&#xff1a;使用 知识点五&#xff1a;可重载和不可重载的运算符 可重载运算符 算数运算符 #region 算数运算符//注意 符号需要两个参数还是一个参数public static Point operator -(Point p1,Point p2){retur…

【Flutter】Flutter安装和配置(mac)

1、准备工作 升级Macos系统为最新系统安装最新的Xcode电脑上面需要安装brew https://brew.sh/安装chrome浏览器&#xff08;开发web用&#xff09; 2.、下载flutter https://docs.flutter.dev/release/archive?tabmacos 大家网页后&#xff0c;选择对应的版本【Tips&#x…

VXLAN 为何采用UDP

VXLAN 简介 VXLAN是一种网络虚拟化技术&#xff0c;它通过在UDP数据包中封装MAC地址和IP信息&#xff0c;使得二层网络&#xff08;如以太网&#xff09;能够跨越三层网络&#xff08;如IP网络&#xff09;进行扩展。这种封装方式不仅支持TCP流量的传输&#xff0c;还能有效处…

Charles - 夜神模拟器证书安装App抓包-charles监控手机出现unknown 已解决

1.Openssl安装 http://slproweb.com/products/Win32OpenSSL.html exe下载安装后进行配置 新建系统变量OPENSSL_HOME&#xff0c;变量值设为(绝对路径)软件安装目录下的bin 直接浏览 编辑用户变量path&#xff0c;新建%OPENSSL_HOME%&#xff0c;最后点击确定 查看openssl版本&a…

读懂以太坊源码(4)-详细解析节点配置文件geth.toml

要读懂以太坊源码&#xff0c;先熟悉配置文件的每个配置项也是非常有必要的&#xff0c;以下代码是以太坊主网配置文件(geth.toml)的完整内容&#xff0c;后面是对每个配置项的说明&#xff1a; [Eth] NetworkId 0 SyncMode "snap" EthDiscoveryURLs [] SnapDisc…