flutter 专题二十四 Flutter性能优化在携程酒店的实践
Flutter性能优化在携程酒店的实践
一 、前言
携程酒店业务使用Flutter技术开发的时间快接近两年,这期间有列表页、详情页、相册页等页面使用了Flutter技术栈进行了跨平台整合,大大提高了研发效率。在开发过程中,也遇到了一些性能相关问题和用户反馈,比如长列表滚动卡顿、页面打开时间较长、页面打开后部分数据加载时间较长等问题。为解决这些问题,我们选用了多个性能指标监控业务运行状态,借助性能检测工具定位问题,并查阅源码、文档等资源解决问题,形成了这篇文章。
同时在不断的需求迭代和代码更新过程中,APP的性能稳定性持续受到挑战,为此我们建立了线上性能监控系统,通过量化,治理,监控三方面手段,持续改善APP性能和用户体验。目前页面的各种性能指标诸如FPS、TTI、内存等都达到了不错的效果,本文将介绍我们在优化过程中所遇到的问题和采取的主要优化方案。
二、FPS与TTI性能优化
2.1 常用性能指标和卡顿定义
对于客户端应用来说,流畅度是影响用户使用体验的关键因素。流畅度低主要有:低FPS、高TTI、卡顿。这些现象出现时,页面会出现不连续的动画,页面刷新会短暂停顿,打开新页面速度较慢,新页面出现白屏或者较长时间的加载动画,用户做点击滑动等交互时页面不响应。
用户操作 FPS 的定义是每秒传输帧数 (Frames Per Second),是图像领域的概念。对于手机客户端来说,主流显示屏的刷新率为60Hz,高端手机显示屏刷新率可以达到120Hz及以上。理想情况下,页面绘制的FPS和屏幕刷新率一致。屏幕画面刷新次数越多,屏幕可以展示的动态细节越多,所以数值越高越好。TTI的定义是从页面加载开始到页面处于完全可交互状态 (Time To Interactive),完全可交互状态指的是页面有内容呈现并且用户可以进行操作。
2.2 FPS优化工具
Flutter官方提供了三种应用编译选项,debug模式、release模式和profile模式。当我们需要做性能分析的时候,需要打包profile模式的应用,这个模式的性能接近release模式,并且有性能相关的信息分析。我们使用的工具是官方提供开发者工具中的Performance View,并选择了Enhance tracing模式。
上图是帧渲染时间,横坐标是帧号,纵坐标是绘制时间,蓝色代表该帧满足60fps,橙色代表不满足60fps。从这张图可以快速定位到绘制时间较长的帧,而下图是选中某帧之后,UI绘制和光栅化时间,如果选择了Enhance tracing模式,可以看到耗时较长的方法、widget build。
前文已经介绍过FPS的定义,对于Flutter绘制而言,每帧绘制耗时前三的是UI绘制时间、光栅化时间、vsync ahead。UI绘制时间主要是widget build、layout、paint,简单认为是CPU时间;光栅化时间可以简单认为是GPU时间;vsync ahead是vsync信号与widget build之间的延时。
2.3 实践方案
优化1,控制setState次数,使用Provider机制减小刷新范围
我们的业务开发是MVVM结构的,数据驱动UI更新。UI的绘制占了性能开销的很大部分,减少不必要的UI绘制、控制UI绘制的范围这两种方法能显著改善性能。
减少不必要的UI绘制是通过控制build次数实现的。widget build是通过setState方法或者builder方法触发的,在业务中,尽量减少非必要的setState,只有真正页面数据发生变化,页面状态变化时才调用setState方法。对于builder方法,可以实现shouldRebuild等接口,增加触发builder方法的限制。
控制UI绘制的范围是通过改变widget树层级实现的。MVVM中数据触发UI更新的方式有很多,我们的业务主要用到了Provider机制,这是一种观察者模式设计。如下图所示,对于左边的widget树,如果只需要更新Container容器配置和Icon图标配置,那么可以将selector拆分到这两个widget的双亲widget,实现了Text widget不刷新。对于widget树较大的业务,这样的改动能显著提升FPS。
优化2,预构建widget (AnimatedBuilder)
上图是酒店详情页头部沉浸式动画的UI,头部展开的过程中,图片和图片上的蒙层需要重新绘制,图片上部SHA logo不需要重新绘制,图片下部tab栏不需要重新绘制,对于这个需求的做法是用AnimatedBuilder。
AnimatedBuilder提供了几个可选参数,animation是对动画的监听,builder是动画过程中需要重新绘制的部分,child是动画过程中不需要重新绘制的部分,child作为参数会传入builder中。下面的伪代码是一个例子,动画过程中Text并不会多次绘制。
@overrideWidget build(BuildContext context) {return AnimatedBuilder(animation: _controller,child: Container(width: 200.0,height: 200.0,color: Colors.green,child: const Center(child: Text('Text!'),),),builder: (BuildContext context, Widget child) {return Transform.rotate(angle: _controller.value * 2.0 * math.pi,child: child,);},);}
对于详情页头部沉浸式动画的例子,可以把widget树进行拆分,只有图片和图片蒙层放入builder方法中,其余的widget作为child传入builder,同时用Stack widget实现两部分UI的组合,这样改进之后,FPS在动画过程中有较大提升。
优化3,const widget
对于dart语法,需要分清楚final和const关键字的区别。关键字final的意思是一次赋值,不能改变;而关键字const的意思是常量,确定的值。这两者的区别是final变量在第一次使用时被初始化,而const 变量是一个编译时替换为常量值。同样的,对于const widget,这个widget在编译阶段就已经确定,不会有状态的变化和成员变量更新。const widget特别适合于标签、特殊Icon等可以复用的UI,性能开销较小。
优化4,减少耗时计算,放到Isolate中
Flutter应用中的Dart代码执行在UI Runner中,而Dart是单线程的,我们平时使用的异步任务Future都是在这个单线程的Event Queue之中,通过Event Loop来按顺序执行。需要避免将一些耗时计算放在UI线程,可以把耗时计算放到Isolate去执行。
优化5,懒加载
能够实现懒加载的有ListView.builder、PageView.builder和GridView.builder,这些widget可以用户长列表或重复容器结构的UI,通过判断单个item是否在屏幕内或者将要进入屏幕位置而进行绘制。与之对应的是Column、Row等一次性绘制widget,对于重复结构的数据,尽量避免使用这些组件。
如下图中,酒店周边景点美食购物列表和附近同类型酒店列表都实现了按需加载。酒店周边景点美食购物列表的卡片数量超过20个,最初使用Row 组件构建时,第一次构建时间超过25ms,达不到60FPS的16ms绘制时间要求。当然,按需加载也有性能开销,出现在列表的滑动过程中。如果一次性全部构建了列表,滑动过程中不会触发新的构建,滑动流畅度体验更好,但是第一次构建时的卡顿感明显。
优化6,分帧渲染
错峰加载方案使用分帧渲染,分帧渲染的原理是将一棵Widget树中的部分绘制时间较长的节点在第一帧时只占位不绘制,等到下一帧开始时,节点替换占位UI,单独使用一帧时间绘制。
在酒店详情头部信息绘制中运用了分帧渲染技术,下左图未使用分帧渲染,下右图对图片tab栏、酒店设施标签、点评模块、地址栏使用分帧渲染。从结果看,减少了3次卡顿和1次轻微卡顿,流畅帧占比超过90%。
布局与绘制的基本单位是一棵widget树,分帧渲染的原理是将布局与绘制时间较长的子widget先用Container占位,再等下一帧开始时单独渲染。使用占位widget的伪代码如下,build方法返回占位widget,并在widget构建帧结束时替换占位widget并触发绘制。
@overridevoid initState() {super.initState();result = widget.placeHolder;replaceWidget ();}@overrideWidget build(BuildContext context) {return result;}void replaceWidget() {SchedulerBinding.instance.addPostFrameCallback((t) {TaskQueue.instance.scheduleTask(() {if (mounted)setState(() {result = widget.child;});}, Priority.animation, id: widget.index);});}
帧的绘制状态可以从SchedulerBinding获得,同时建立队列保证一帧执行一个子widget绘制。
// 等待当前帧结束时替换占位widget并触发绘制await SchedulerBinding.instance.endOfFrame;// 执行任务队列中的绘制任务final TaskEntry<dynamic> entry = _taskQueue.first;entry.run();
2.4 GPU 问题定位与优化
GPU 问题主要集中在底层渲染耗时上。有时候 Widget 树虽然构造起来容易,但在 GPU 线程下的渲染却很耗时。涉及 Widget 裁剪、蒙层这类多视图叠加渲染,或是由于缺少缓存导致静态图像的反复绘制,都会明显拖慢 GPU 的渲染速度。可以使用性能图层提供的两项参数,负责检查多视图叠加的视图渲染开关checkerboardOffscreenLayers和负责检查缓存的图像开关checkerboardRasterCacheImages来检查这种模块的存在。
优化1,checkerboardOffscreenLayers
多视图叠加通常会用到 Canvas 里的savaLayer 方法,这个方法在实现一些特定的效果(比如半透明)时非常有用,但由于其底层实现会在 GPU 渲染上涉及多图层的反复绘制,因此会带来较大的性能问题。对于 saveLayer 方法使用情况的检查,我们只要在 MaterialApp 的初始化方法中,将checkerboardOffscreenLayers 开关设置为 true,分析工具就会自动帮我们检测多视图叠加的情况了,使用了 saveLayer 的 Widget 会自动显示为棋盘格式,并随着页面刷新而闪烁。
不过,saveLayer 是一个较为底层的绘制方法,因此我们一般不会直接使用它,而是会通过一些功能性 Widget,在涉及需要剪切或半透明蒙层的场景中间接地使用。所以一旦遇到这种情况,我们需要思考一下是否一定要这么做,能不能通过其他方式来实现。如下图所示,因为详情头部bar用到高斯模糊,同时使用ClipRRect裁切圆角,ClipRRect会调到savelayer接口,所以该部分产生闪烁。
优化2,checkerboardRasterCacheImages
从资源的角度看,另一类非常消耗性能的操作是,渲染图像。这是因为图像的渲染涉及 I/O、GPU 存储,以及不同通道的数据格式转换,因此渲染过程的构建需要消耗大量资源。
为了缓解GPU 的压力,Flutter 提供了多层次的缓存快照,这样Widget 重建时就无需重新绘制静态图像了。与检查多视图叠加渲染的checkerboardOffscreenLayers 参数类似,Flutter 也提供了检查缓存图像的开关 checkerboardRasterCacheImages,来检测在界面重绘时频繁闪烁的图像(即没有静态缓存)。
我们可以把需要静态缓存的图像加到 RepaintBoundary 中,RepaintBoundary 可以确定 Widget 树的重绘边界,如果图像足够复杂,Flutter 引擎会自动将其缓存,避免重复刷新。当然,因为缓存资源有限,如果引擎认为图像不够复杂,也可能会忽RepaintBoundary。
2.5 页面预加载提升TTI
网页应用的主要流程有三步,通过链接打开页面,发送服务请求获得页面数据,将页面数据展示在页面上。对客户端应用来说,页面之间跳转是相对确定的,数据在页面之间存在共享的可能,预加载的工作是在打开页面之间预先获得页面的数据,从而减少打开页面到页面展示的时间。
预加载数据有三种常见方法,第二个页面的数据在第一个页面的服务结果中获得;第二个页面的数据在客户端其它页面中预先获得并缓存;第二个页面的服务请求在打开页面之前发送。
优化1,预加载页面数据
页面数据预获取的方案,实现方法是在上一个页面提前获取服务数据,在用户跳转到当前页面时,直接从缓存获取,节省了数据的网络传输时间,达到快速展示当前页面内容的效果。目前在酒店核心预订流程,都运用了数据预加载技术,如下图所示。
结合酒店业务特点,数据预加载需要考虑几个方面问题,第一,酒店预订流程页面PV量都很高,酒店列表和详情页PV都是千万级别,所以需要考虑数据预加载的时机,避免服务的资源浪费。第二,酒店列表,详情,填单页都有价格信息,价格信息对用户来说是动态信息,实时都有变价可能,所以需要考虑数据预加载的缓存策略,避免因为价格的前后不一致造成用户误解。
在实现全流程预加载方案之后,我们酒店预订流程页面的慢加载率从初始值的42.90%降低至现阶段的8.05%。
优化2,预加载ViewModel
与数据预获取的方案相比,预加载ViewModel更进一步,将预获取的数据处理成ViewModel形式,在打开页面时直接用ViewModel进行展示。这种方案减少了业务对数据处理的时间。
上图是杭州绿城尊蓝钱江豪华精选酒店在酒店列表页和酒店详情页头部的UI对比。可以看出,酒店详情页头部的信息主要是酒店名称、星级、榜单、特色设施、点评、开业装修时间等信息,这些信息和列表页酒店卡片信息存在重合。如果用户浏览的轨迹为从酒店列表页到酒店详情页,那么可以直接将列表页的数据带入酒店详情页作为头部展示。
上图为详情页头部预加载的主要流程。我们的flutter业务代码采用MVVM的结构,将服务请求的结果处理完的数据放入ViewModel中,ViewModel的数据更新通过Provider机制触发页面UI更新。
图中可以开到,详情页头部ViewModel的数据有两个来源,分别是列表页服务请求的结果和详情页服务请求的结果。这两个服务请求结果到ViewModel的业务流程不一样,列表页的服务结果数据通过URL参数的方式传入详情页,而详情页服务结果可以直接生成详情页头部的ViewModel。
图中还有一个重要模块是列表页服务结果和详情页服务结果之间的通用缓存DataCache,它的功能是实现页面之间数据的一致性。页面上的数据可以由服务更新,也可以由用户交互更新。业务的ViewModel依赖这个通用缓存,数据更新会触发页面UI更新。
三、Flutter通道优化
3.1 背景
因为我们APP采用的私有服务协议,目前发服务的动作还是在Native代码上,而酒店的核心页面已经转到了Flutter上。通过Flutter框架提供的通道技术Native到Flutter的数据传输通道需要对数据做一次额外的序列化及反序列化的传输,同时传输的过程比较耗时,会阻塞UI的渲染主线程,对页面的加载会造成明显的影响。我们检测到这个环节之后和框架一起对Flutter的底层框架进行了改造,可以实现数据流直接的透传,同时不阻塞UI主线程,性能得到了极大的提升。
优化前,通过服务返回的数据流传递到Flutter使用,整个过程要经历以下4步:
- PB反序列化
- Response到JsonString的编码
- JsonString到MethodChannel(使用JsonMethodCodec编解码)
- 传输JsonString到Reponse的解码
整个过程链路长,数据传输量大,效率低,影响到页面加载性能,如下图所示。
改造后,通过服务返回的数据流,直接传输到Flutter侧,在Flutter直接进行PB的反序列化,传输性能得到极大提升。
- PB的数据流到MethodChannel(使用StandardMethodCodec编解码)传输
- PB反序列化到Response
整个过程链路短,数据传输量小,效率高,如下图所示:
其中MethodChannel的编解码器由JsonMethodCodec换成了StandardMethodCodec。因为StandardMethodCodec可以避免转换JsonString的操作,能节省传输时间。
3.2 Flutter中使用Protobuf
在flutter中使用Protobuf,首先需要将proto契约文件转化成dart文件,可以借助官方编译工具protoc进行编译。
1,获取protoc工具
首先,使用如下的命令安装工具。
sudo apt-get install autoconf automake libtool curl make g++ unzip
然后,再安装Protobuf发行版
https://github.com/protocolbuffers/protobuf/releases
下载完成之后,解压,进到目录中执行下面命令编译安装。
./configure
make
make check
sudo make install
sudo ldconfig # refresh shared library cache.
接着,再安装protoc-gen-dart插件。
dart pub global activate protoc_plugin
在Terminal中执行protoc命令生成dart文件。
protoc --dart_out=. <文件名>.proto
2,使用生成的dart契约文件
执行flutter pub add protobuf命令,修改项目的pubspec.yaml,在dependencies中加上: protobuf: ^2.0.1
。接着,编写如下测试代码:
其中,生成Person的类继承了Protobuf包里的GeneratedMessage类,序列化和反序列化由基类实现。但是这种方式不能根据需要定制化生成契约文件。因此,为了更好的兼容Json格式的数据,可以使用FreeMarker模板引擎定制化生成契约文件。
3.3 使用FreeMarker定制化生成dart契约文件
FreeMarker是一款模板引擎:即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
下面介绍如何使用FreeMarker和protoc命令生成任意编程语言的契约文件:
- 下载FreeMarker最新版jar包:Download / Maven - Apache FreeMarker™。
- 下载Protobuf对应版本的jar包:https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java
- 在Java项目中导入对应jar包。
- 编写Java程序
程序的流程如上图所示。首先使用protoc命令生成对应的描述文件,其次将描述文件转换成对应java对象,最后使用FreeMarker模板引擎生成任意语言的契约文件。
由上图可知,模板引擎的输入是一个classModel对象。如下图实现了将描述文件转化成classModel对象的功能。
FTL模板文件如下图所示:
执行代码输出契约文件:
这样就可以实现了根据proto文件自定义生成任意编程语言的契约文件。
3.4 Json与Protobuf的性能对比
我们对比了相同报文情况下Json和Protobuf在序列化和反序列化上所花费的时间。从下图可知,Protobuf在序列化和反序列化相同大小报文时比Json花费的时间大大减少了,也大大提高了我们获取数据的速度。
四、内存泄漏治理
4.1 内存泄漏的常用手段
内存泄漏是一个比较严重的问题,如果出现,对App的稳定性和用户体验都有非常大影响。因此对这块的监控和治理也是我们非常关注的一块。
在监控方面Flutter现在比较通用的方法就是利用Expando中的弱引用去监控我们要检查是否有泄漏的对象,如果出现则从VM中获取其引用链接,从而分析其泄漏原因。我们的框架也利用此方法监控了我们app中的每个页面是否在退出时还存在泄漏。
另外通过Flutter的Dev tool中的内存监控工具也能实现对泄漏对象的发现。比如对于酒店详情页面,反复进入和退出此页面,如果有泄漏会发现,在内存监控工具中出现此页面多个的对象存活,此时基本可以判断出此页面出现了泄漏了。下图的第一列是类名,第二、三列是实例数量,第四、五列是对应分配的字节数。
4.2 内存泄漏治理
下面介绍一下,我们在我们页面的内存泄漏治理中发现的一些导致泄漏的原因和解决的办法。
优化1,调用Native的Plugin时,对Future的Then设置的闭包没有关闭
在调用Native的Plugin接口时,有时会设置一个Then的闭包,期望在这个闭包里去处理这个Plugin的返回结果。这个闭包会注册到引擎的全局变量里面,如果Native调用了result的listener,这个Then的闭包会走到,然后会被清除掉。如果某些case,Native没有调用,则这个闭包会泄露,如果这个闭包所属的Model能引用到页面对象的话,则会造成整个页面的泄露。
比如下面这个例子,我们进入flutter页面时会调这个plugin,但是native对应的result则必须在某些case情况下才会回调。而大部分情况下,是不会回调的,从而造成整个页面的泄露。解决方法是把future转换成stream,然后我们在页面退出时cancel掉,就能避免闭包的泄漏。
例子:调用Native的Plugin时出现泄漏的情况。Flutter侧的调用代码如下:
void callNative() {
FlutterBridge.callNative("method", map).then((value) {do some thing;});
}
Native的响应代码如下:
override fun flutterPluginAction ( result: MethodChannel.Result){
if (condition) {result.success(ret)} else {do something;}
}
可以看到Native在接受到这个plugin调用时,对于result的调用返回不是一直都会做的,它需要等到满足条件才会做这件事情,而如果它不做这件事情,对应的flutter那边的闭包就会一直被保存在引擎中,这个引用链也会一直存在,从而造成这个引用链上的对象都泄漏了。
解决的方法是:
void callNative () {
Future future = FlutterBridge. callNative ("method");_streamSubscription?.cancel();_streamSubscription = future?.asStream()?.listen((value)
{do something;
});
}
我们的解决方式,就是对这种异步但不能确定回调是否一定完成的情况,换成用StreamSubscription去监听。然后当页面退出时做一下cancel的动作,这样就能避免泄漏的发生。
void onPageDestroy() {_ streamSubscription?.cancel();
}
这种等待对异步调用的回调监听其实都可能存在类似问题,只不过如果是单纯在Dart中的异步调用一般不会存在这种不回调的情况。但是对于plugin这种跟native的交互的地方,我们在初期接触flutter时没有关注到这块,有可能会造成遗漏。
优化2,一些观察者模式中的订阅者在页面退出时没有取消订阅
这种是大家比较熟悉的一种情况。常见的例子有例如像Timer,EventBusCenter.defaultBus和LifeCycleObserver等。这些订阅者如果在页面退出时不需要了,需要记得取消掉。否则也会造成内存泄漏,这种情况我们也应该避免。
五、小结
性能优化是一件不断持续,不断深入的事情。我们通过本文中所介绍的改进措施对页面性能实现了很大的优化,达到了不错的效果。后续也会在此基础之上对还可提高的地方继续加深,同时也会对已经验证实行有效的方案去做一些抽象,封装工作,后续提供通用的解决方案。
相关文章:
flutter 专题二十四 Flutter性能优化在携程酒店的实践
Flutter性能优化在携程酒店的实践 一 、前言 携程酒店业务使用Flutter技术开发的时间快接近两年,这期间有列表页、详情页、相册页等页面使用了Flutter技术栈进行了跨平台整合,大大提高了研发效率。在开发过程中,也遇到了一些性能相关问题和…...
RAFT:随机退火森林
RAFT:随机退火森林 RAFT(Randomized Annealed Forests)是一种机器学习算法,主要用于分类和回归任务。以下是对它的介绍及原理举例说明: 一、RAFT简介 RAFT是一种基于随机森林的集成学习方法,它结合了随机森林的优点和退火算法的思想。随机森林通过构建多个决策树并综…...
上下游服务间解耦的技术与管理
一、解耦为何至关重要 在当今软件研发的复杂生态中,耦合问题如影随形,困扰着众多开发者与企业。当多个模块、系统或团队紧密交织,相互依赖程度不断攀升,仿佛一张错综复杂的网,牵一发而动全身。就拿电商系统来说&#…...
[桌面运维]windows自动设置浅深色主题
设置自动浅色/深色主题 我看很多up主的教程过于繁琐,需要添加四个功能,并且有些还不能生效! 大多数都是教程: 自动任务栏浅色 add HKCUSOFTWAREMicrosoftWindowsCurrentVersionThemesPersonalize/v SystemUsesLightTheme /t …...
【Spring】Spring DI(依赖注入)详解——注入参数的细节处理-内部Bean的注入
引言 在现代Java开发中,Spring框架已经成为了构建企业级应用的标准工具之一。Spring的核心特性之一就是依赖注入(Dependency Injection,DI),它通过将对象的依赖关系从代码中解耦出来,提升了代码的可维护性…...
Docker: 教程07 - ( 如何对 Docker 进行降级和升级)
如果我们使用 docker 来管理容器,那么保持 docker 引擎的更新将会是十分重要的,这一篇文章我们将会讨论如何对Docker 进行降级和升级。 准备工作 - docker 环境 我们需要拥有一个安装好 docker 的运行环境。 如果你需要了解如何安装 docker 可以通过如…...
CV-LLM经典论文解读|VTimeLLM: Empower LLM to Grasp Video MomentsVTimeLLM:赋能大语言模型理解视频片段
论文标题 VTimeLLM: Empower LLM to Grasp Video Moments VTimeLLM:赋能大语言模型理解视频片段 论文链接: VTimeLLM: Empower LLM to Grasp Video Moments论文下载 论文作者 Bin Huang, Xin Wang, Hong Chen, Zihan Song, Wenwu Zhu (Tsinghua Un…...
新手学习yolov8目标检测小记2--对比实验中经典模型库MMDetection使用方法(使用自己的数据集训练,并转换为yolo格式评价指标)
一、按照步骤环境配置 pip install timm1.0.7 thop efficientnet_pytorch0.7.1 einops grad-cam1.4.8 dill0.3.6 albumentations1.4.11 pytorch_wavelets1.3.0 tidecv PyWavelets -i https://pypi.tuna.tsinghua.edu.cn/simple pip install -U openmim -i https://pypi.tuna.t…...
Kubernetes开发环境minikube | 开发部署apache tomcat web单节点应用
minikube是一个主要用于开发与测试Kubernetes应用的运行环境 本文主要描述在minikube运行环境中部署J2EE tomcat web应用 minikube start --force minikube status 如上所示,在Linux中启动minikube运行环境 service docker start docker version service docker …...
浙江安吉成新的分布式光伏发电项目应用
摘 要:分布式光伏发电站是指将光伏发电组件安装在用户的建筑物屋顶、空地或其他适合的场地上,利用太阳能进行发电的一种可再生能源利用方式,与传统的大型集中式光伏电站相比,分布式光伏发电具有更灵活的布局、更低的建设成本和更高…...
Git - 记录一次由于少输入了一个命令导致的更改丢失
Git - 记录一次由于少输入了一个参数导致的更改丢失 前言 某晚我激情开发了几个小时,中途没有进行commit存档。准备睡觉时,我想创建一个新的分支并将今晚所有更改提交到新分支上(似乎应该开发时候就创建?)。 然后因…...
【Nginx】设置https和http同时使用同一个端口访问
以下是一个同时使用 HTTP 和 HTTPS 并通过 8070 端口的配置示例: server {listen 8070;server_name your_domain.com;location / {root /var/www/html;index index.html;} }server {listen 8070 ssl;server_name your_domain.com;# SSL 证书和私钥的路径ssl_certif…...
Vue 组件开发:构建高效可复用的 UI 构建块
在现代前端开发中,Vue.js 凭借其简洁的 API、渐进式框架设计和强大的生态系统,已经成为众多开发者的首选。Vue 组件化开发是其核心特性之一,它允许我们将复杂的 UI 拆分成多个独立、可复用的组件,从而提高代码的可维护性和可扩展性…...
【Uniapp-Vue3】v-if条件渲染及v-show的选择对比
如果我们想让元素根据响应式变量的值进行显示或隐藏可以使用v-if或v-show 一、v-show 另一种控制显示的方法就是使用v-show,使用方法和v-if一样,为true显示,为false则不显示。 二、v-if v-if除了可以像v-show一样单独使用外,还…...
浏览器报错:您的连接不是私密连接,Kubernetes Dashboard无法打开
问题描述 部署完成Kubernetes Dashboard后,打开HTTPS的web页面,Chrome和Edge浏览器都无法正常加载页面,会提示您的连接不是私密连接的报错。 原因: 浏览器不信任这些自签名的ssl证书,为了…...
asp.net core 属性路由和约定路由
在 ASP.NET Core 中,Web API 中的路由(Route)用于确定客户端请求的 URL 与服务器端处理逻辑之间的映射关系。路由机制在 Web API 的开发中非常重要,它帮助定义和管理不同请求路径如何触发特定的控制器和操作方法。 1. 路由概述 …...
机器学习之模型评估——混淆矩阵,交叉验证与数据标准化
目录 混淆矩阵 交叉验证 数据标准化 0-1标准化 z 标准化 混淆矩阵 混淆矩阵(Confusion Matrix)是一种用于评估分类模型性能的工具。 它是一个二维表格,其中行表示实际的类别,列表示模型预测的类别。 假设我们有一个二分类问题&…...
Java实现UDP与TCP应用程序
三、Java实现UDP应用程序 3.1 InetAddress类 java.net.InteAddress类是用于描述IP地址和域名的一个Java类; 常用方法如下: public static InetAddress getByName(String host):根据主机名获取InetAddress对象public String getHostName()…...
[python3]Excel解析库-calamine,10倍openpyxl性能
calamine 是一个用于读取多种电子表格格式(如 Excel、LibreOffice Calc 等)的 Python 库。它支持 .xls, .xlsx, .ods 和 .csv 文件格式,提供了简单易用的 API 来加载和处理电子表格数据。calamine 的一大特点是它的轻量级和高效性,…...
Clisoft SOS设置Server和Project
Clisoft SOS设置Server和Project 一、关于SOS Servers、Clients、Projects和Work Areas 以下三个图是官方文档中介绍的三种情况 图1:带有两个客户端的SOS服务器 图2:使用本地缓存服务器 图3:远程设计团队的缓存服务器 因为SOS软件需要…...
基于FPGA的出租车里程时间计费器
基于FPGA的出租车里程时间计费器 功能描述一、系统框图二、verilog代码里程增加模块时间增加模块计算价格模块上板视频演示 总结 功能描述 (1);里程计费功能:3公里以内起步价8元,超过3公里后每公里2元,其中…...
AnaConda下载PyTorch慢的解决办法
使用Conda下载比较慢,改为pip下载 复制下载链接到迅雷下载 激活虚拟环境,安装whl,即可安装成功 pip install D:\openai.wiki\ChatGLM2-6B\torch-2.4.1cu121-cp38-cp38-win_amd64.whl...
Hello 2025(A-C)
补题链接:Dashboard - Hello 2025 - Codeforces A. MEX Table 思路 除了含0的列和行其他的都是0,输出max(n,m)1即可 代码 #include<bits/stdc.h> using namespace std;#define vcoistnt ios_base::sync_with_stdio(false); cin.tie(NULL); co…...
Burpsuite20241102macM1版安装
1、安装jdk11 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" brew update brew install openjdk11 echo export PATH"/opt/homebrew/opt/openjdk11/bin:$PATH" >> ~/.zshrc source ~/.zshrc j…...
jenkins入门10--自动化构建
build periodically:设定类似cron周期性时间触发构建 * * * * * (五颗星,中间用空格隔开) 第一颗表示分钟,取值0~59 第二颗表示小时,取值0~23 第三颗表示一个月的第几天,取值1~31 第四颗表示第几月…...
Java基础概念
自动装箱 Integer i 10; //装箱 int n i; //拆箱 普通数据类型:直接在栈内存中分配空间,存储的是具体的值。包装类:作为对象在堆内存中分配空间。包装类实际上是对普通数据类型的封装,每个包装类都包含了对应的数据类…...
57.在 Vue 3 中使用 OpenLayers 点击选择 Feature 设置特定颜色
在 Web 开发中,地图应用是非常常见的需求,而 OpenLayers 是一个非常强大的地图库,它提供了丰富的地图操作功能。今天,我们将一起学习如何在 Vue 3 中结合 OpenLayers 使用点击事件来选择地图上的 Feature,并设置特定的…...
HTML——61. 单行文本框和密码输入框(主讲input元素的type属性)
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>单行文本框和密码输入框</title></head><body><!--input元素的type属性:(必须要有)--> <!--单行文本框:1.type"text"2.可…...
h264之多视点mvc编码及解码过程(JMVC平台举例)
h264标准参考平台JMVC是针对MVC标准的,JMVC支持多视点编码、合流、多视点解码操作。可以利用JMVC生成h264 mvc码流和解码。 JMVC的下载地址是:jvet / JMVC GitLabH.264/AVC multi-view coding (MVC) extension JMVC reference softwarehttps://vcgit.hh…...
深度学习blog-深刻理解线性变换和矩阵
深度学习中避免不了矩阵运算,或者张量(其实是矩阵数组)运算。卷积是矩阵加、乘法,注意力也是一样。本质都一样,所谓注意力,卷积、滤波,是对不必了解数学的人说的,底层都是矩阵运算&a…...
C语言 扫雷程序设计
目录 1.main函数 2.菜单打印menu函数 3.游戏game函数 4.宏定义 5.界面初始化 6.打印界面 7.设置雷 8.统计排查坐标周围雷的个数 9.排查雷 10.总代码 test.c代码 game.h代码 game.c代码 结语: 一个简单的扫雷游戏,通过宏定义可以修改行列的…...
[笔记] Jenkins 安装与配置全攻略:Ubuntu 从零开始搭建持续集成环境
随着 DevOps 流程的普及,持续集成(CI)和持续交付(CD)已成为现代软件开发中不可或缺的一部分。Jenkins 作为一款开源的自动化服务器,广泛应用于 CI/CD 管道的构建与管理。它不仅支持多种编程语言和工具链&am…...
【51单片机零基础-chapter3:按键:独立按键|||附带常见C语句.逻辑运算符】
将unsigned char var0;看作沟通二进制和十进制的桥梁 var是8位,初始为0000 0000; 同时可以进行十进制的运算 逻辑运算 位运算 & 按位与(有0则0) | 按位或(有1则1) ~ 按位非 ^ 按位异或(相同则1,不同为0) <<按位左移 >>按位右移 位运算符解释: 0011 1100 <&…...
深入浅出:深层网络处理技术的教学指南
引言 在人工智能的浪潮中,深层网络处理技术(Deep Learning)无疑是最耀眼的明星之一。无论是图像识别、自然语言处理,还是语音识别,深层网络都展现出了强大的能力。然而,对于初学者来说,深层网络…...
深入浅出Node.js-1(node.js入门)
全新专栏带你快速掌握node.js Node.js入门 html,css,js 30年了 nodejs环境 09年出现 15年 nodejs为我们解决了2个方面的问题: 【锦上添花】让我们前端工程师拥有了后端开发能力(开接口,访问数据库) - 大公司BFF(5…...
Django的runserver
当年执行 python manage runserver命令时 1. 先执行 runserver 中的 handle方法 2. 执行 self.run()方法 3. 执行 self.inner_run() 3.1 inner_run 下 run方法的封装 3.1.1 接着看 handle 怎么来的 封装了一个方法 接着找返回函数 3.1.2在 basehttp 下 3.1.3 get_wsgi_appl…...
MySQL 存储引擎
InnoDB InnoDB是MySQL的默认存储引擎,自MySQL 5.5版本起开始使用。它提供了具有提交、回滚和崩溃恢复能力的事务安全(ACID兼容)存储引擎。 主要特性: 事务支持:完全支持ACID(原子性、一致性、隔离性、持久…...
React知识盲点——组件通信、性能优化、高级功能详解(大纲)
组件通信 React 组件通信详解 在 React 中,组件通信是一个核心概念,主要指的是如何让不同的组件共享和传递数据。React 提供了多种机制来实现组件间的数据传递和状态共享。以下是几种常见的组件通信方式,包括:父子组件通信&…...
Maven 详细配置:Maven 项目 POM 文件解读
Maven 是 Java 开发领域中广泛使用的项目管理和构建工具,通过其核心配置文件——POM(Project Object Model)文件,开发者能够定义项目的基本信息、依赖关系、插件配置以及构建生命周期等关键要素。POM 文件不仅是 Maven 项目的核心…...
selenium
pythonselenium selenium是一个第三方库,python有很多库; 1、什么是ui自动化? 通过模拟手工操作用户ui页面的方式,用代码去实现自动化操作和验证的行为。 2、ui自动化的优点? (1)解决重复性的功能测试…...
网络安全:设备原理与操作
设备型号概述 网络安全企业有哪些? 国外:思科,Juniper,惠普,3Com,。。。。 国内:华为,中性,锐捷,蓝盾,绿盟,山石网科,36…...
pytorch中nn.Conv2d详解及参数设置原则
文章目录 基础参数1. in_channels (输入通道数)2. out_channels (输出通道数)3. kernel_size (卷积核大小)4. stride (步幅)5. padding (填充)6. dilation (膨胀)7. groups (分组卷积)8. bias (偏置) 如何设置参数?1. **in_channels 和 out_channels(输入…...
select下拉框,首次进入页面没有显示value的情况
bug场景: 类似这种bug情况排查如下: 首先 理解含义 options就是存放键值对的,id就是key,对上了它就自动把label显示 而且如果你用来当作key和label的字段,与后端返回的不一致,还可以进行更改 其次 排查接…...
接口项目操作图-thinkphp6-rabbitmq
一、用户开户流程 用户首次需要联系商务开通账户,需要提供手机号及来访问的IP。开好户之后,平台方将提供用户访问的key值及header头部参数的公钥加密文件、body访问参数以及返回数据的公私钥加解密文件。 二、用户请求流程 用户将拿到的key值进行rsa公钥…...
thinkphp6.0常用设计模式实例
单例模式 (Singleton) 场景:确保一个类只有一个实例,并提供一个全局访问点。 实际业务:数据库连接、日志记录器、配置管理等。 ThinkPHP 6.0 实现: namespace app\common;class DatabaseConnection {private static $instance …...
微服务保护——Sentinel
什么是微服务保护? 微服务保护是一系列用于保障微服务架构稳定、可靠运行的策略与技术手段,在复杂的分布式微服务系统里,它能避免局部故障引发连锁反应,从而维持整个系统的可用性,主要涵盖以下几个关键部分:…...
php 多进程那点事,用 swoole 如何解决呢 ?
在 PHP 中,多进程的处理通常会遇到一些挑战,比如资源共享、进程间通信、性能优化等。Swoole 是一个高性能的协程和多进程框架,旨在为 PHP 提供异步、并发、协程等功能,解决了传统 PHP 环境中的多进程管理问题。通过使用 Swoole&am…...
STM32+ADC+DMA快速循环转换
测试平台:STM32F405RGT6 uint32_t AD_Buf[100]{0}; HAL_ADC_Start_DMA(&hadc2,(uint32_t *)AD_Buf,100); while(1) {printf("AD_Buf:%d\n",AD_Buf[0]); }...
移动电商的崛起与革新:以开源AI智能名片2+1链动模式S2B2C商城小程序为例的深度剖析
摘要:本文旨在探讨移动电商的崛起背景、特点及其对传统电商模式的革新影响,并以开源AI智能名片21链动模式S2B2C商城小程序为具体案例,深入分析其在移动电商领域的创新实践。随着移动互联网技术的飞速发展,移动电商已成为电商行业的…...
QT实现 端口扫描暂停和继续功能 3
上篇QT给端口扫描工程增加线程2-CSDN博客 为按钮pushButton_Stop添加clicked事件,功能为暂停扫描,并在暂停后显示继续按钮,点击继续按钮之后继续扫描 1.更新UI 添加继续按钮 点击转到槽则会自动声明 2. 更新 MainWindow.h 需要新增的部分…...