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技术栈进行了跨平台整合,大大提高了研发效率。在开发过程中,也遇到了一些性能相关问题和…...

L28.【LeetCode笔记】移动零(三种解法)
目录 1.题目 2.向前覆盖法 分析 代码 提交结果 3.优解:双指针 代码 提交结果 4.其他不符合题意的方法:使用队列 代码 提交结果 1.题目 https://leetcode.cn/problems/move-zeroes/description/ 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾…...

jenkins入门10--自动化构建
build periodically:设定类似cron周期性时间触发构建 * * * * * (五颗星,中间用空格隔开) 第一颗表示分钟,取值0~59 第二颗表示小时,取值0~23 第三颗表示一个月的第几天,取值1~31 第四颗表示第几月…...

el-table拖拽表格
1、拖拽插件安装 npm i -S vuedraggable // vuedraggable依赖Sortable.js,我们可以直接引入Sortable使用Sortable的特性。 // vuedraggable是Sortable的一种加强,实现组件化的思想,可以结合Vue,使用起来更方便。 2、引入拖拽函数…...
如何轻松反转C# List<T>中的元素顺序
在C#中,有多种方法可以反转 List<T> 的元素顺序。以下是几种常见的方法: 方法一:使用 List<T>.Reverse 方法 List<T> 类提供了一个内置的 Reverse 方法,可以就地反转列表中的元素顺序。 using System; using…...
Transformer中Self-Attention以及Multi-Head Attention模块详解(附pytorch实现)
写在前面 最近在项目中需要使用Transformer模型来处理图像任务,所以稍微补充一下这部分的知识,本篇主要了解一下Self-Attention以及Multi-Head Attention模块。 原论文链接:https://arxiv.org/pdf/1706.03762 原文代码:tensor2…...

在Nvidia Jetson ADX Orin中使用TensorRT-LLM运行llama3-8b
目录 背景:步骤 1.获取模型权重第 2 步:准备第 3 步:构建 TensorRT-LLM 引擎 背景: 大型语言模型 (LLM) 推理的关键瓶颈在于 GPU 内存资源短缺。因此,各种加速框架主要强调减少峰值 GPU 内存使…...
六十一:HTTP/2的问题及HTTP/3的意义
随着互联网的快速发展,网络协议的升级成为优化用户体验和提升网络效率的重要手段。HTTP/2 于 2015 年发布,标志着超文本传输协议的重大改进。然而,尽管 HTTP/2 带来了许多新特性,它也存在一定的问题。在此背景下,HTTP/…...

IOS开发如何从入门进阶到高级
针对iOS开发的学习,不同阶段应采取不同的学习方式,以实现高效提升.本文将iOS开发的学习分为入门、实战、进阶三个阶段,下面分别详细介绍. 一、学习社区 iOS开源中国社区 这个社区专注于iOS开发的开源项目分享与协作,汇集了大量开…...

非一般的小数:小数的概念新解、小数分类、浮点数的存储
非一般的小数:小数的概念新解、小数分类、浮点数的存储 一、小数的概念二、小数的分类1.有限小数、无限循环小数、无限不循环小数2.纯小数、带小数3.定点数、浮点数 三、浮点数的存储 一、小数的概念 这还用解释吗?小…...

关于游戏销量的思考
1、黑神话达到2300万套,分析师上调预期到超过100亿营收。 以往的我的世界、小鸟、超级食肉男孩等游戏也都是几千万,上亿的销量。 也改变了相关开发者的命运。 一个开发者,卖出一个30万,或100万销量的作品,就足够改变…...
JuiceFS 详解:一款为云原生设计的高性能分布式文件系统
JuiceFS 详解:一款为云原生设计的高性能分布式文件系统 1. 什么是 JuiceFS? JuiceFS(Juiced File System)是一款高性能、POSIX 兼容的云原生分布式文件系统。它采用对象存储作为底层存储,支持多种元数据引擎…...
百度Android面试题及参考答案 (下)
Executorservice 和 Executor 有什么区别? Executor 接口 Executor 是一个简单的接口,它定义了一个方法execute(Runnable command)。这个接口的主要目的是将任务的提交和任务的执行分离,它提供了一种通用的方式来执行一个Runnable任务,但是它没有提供更多高级的功能,比如任…...

RK3588+FPGA全国产异步LED显示屏控制卡/屏幕拼接解决方案
RK3588FPGA核心板采用Rockchip RK3588新一代旗舰 级八核64位处理器,支持8K视频编解码,多屏4K输出,可实现12屏联屏拼接、同显、异显,适配多种操作系统,广泛适用于展览展示、广告内容投放、新零售、商超等领域实现各种媒…...
Elasticsearch:Query rules 疑难解答
作者:来自 Elastic Kathleen_DeRusso 查询规则(Query rules)为用户提供了一种对特定查询进行细粒度控制的方法。目前,查询规则的功能允许你将你选择的搜索结果固定在结果集的顶部,和/或根据上下文查询数据从结果集中排…...

四、VSCODE 使用GIT插件
VSCODE 使用GIT插件 一下载git插件与git Graph插件二、git插件使用三、文件提交到远程仓库四、git Graph插件 一下载git插件与git Graph插件 二、git插件使用 git插件一般VSCode自带了git,就是左边栏目的图标 在下载git软件后vscode的git插件会自动识别当前项目 …...
键盘鼠标共享工具Barrier(kail与windows操作系统)
键鼠共享工具Barrier(kail与windows操作系统)_barrier软件-CSDN博客 sudo apt install barrier...
QTcpSocket 中设置接收缓冲区大小
在 QTcpSocket 中设置接收缓冲区大小 使用setSocketOption方法 在QTcpSocket类中,可以使用setSocketOption函数来设置接收缓冲区大小。具体来说,对于 TCP 套接字,你可以使用QAbstractSocket::ReceiveBufferSizeSocketOption选项。以下是一个简…...
Arduino IDE刷微控制器并下载对应固件的原由
在使用Arduino IDE刷写某个微控制器时,下载对应的固件通常是为了确保微控制器能够正确识别和执行Arduino IDE中编写的代码。以下是对这一过程的详细解释: 一、固件的作用 固件是微控制器或嵌入式设备上运行的软件,它负责控制硬件设备的操作…...
Jurgen提出的Highway Networks:LSTM时间维方法应用到深度维
Jurgen提出的Highway Networks:LSTM时间维方法应用到深度维 具体实例与推演 假设我们有一个离散型随机变量 X X X,它表示掷一枚骰子得到的点数,求 X X X 的期望。 步骤: 列出 X X X 的所有可能取值 x i x_i xi(…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...

【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...