flutter 卡顿_Flutter渲染性能优化全攻略(解决应用卡顿)

本文深入探讨Flutter应用的渲染性能优化,重点关注FPS和每秒帧数对用户体验的影响。通过理解Flutter框架和引擎层的工作原理,提出通过合理使用`const`关键词、避免不必要的组件重建和管理着色器编译来提升性能。文章提供实战案例,包括使用`const`减少Widget重建、避免使用`Opacity`和注意组件底层的`saveLayer()`调用,以及预编译SkSL着色器以减少首次运行时的卡顿。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大规模应用开发过程中,性能优化是重中之重,其中包括了包体积,UI 渲染、交互等多个方面。

在之前的文章中,我通过 Flutter 应用的混淆为入口,探讨了应用包体积优化的实践方案,本文,我把话题再次转到渲染性能上来。

这里,也会涉及到一个非常关健的概念 ——「FPS,Frame Per Second」即「每秒展示帧数」,它代表了应用的流畅度。我们知道,动画和物体动态的运动都是由在一段时间内一系列连续变化的静态帧构成的。

在考虑应用的渲染性能时,我们就是在试图分析应用每秒渲染的帧数。

从物理角度看,对于连续的一系列图像帧,人脑会根据眼睛发出的视觉信号做出反应,一个个静态帧的切换到达一定速度后,就可以欺骗我们的大脑,让我们以为它们是连续的,FPS 就是图像帧切换的速度单位。因此有人说,物体运动的概念其实就是一种思维的束缚。

当 FPS 达到 10-12 时,大脑便可以感知运动,但此时并不流畅,达到 24 FPS 时,人眼就能看到流畅的运动了,但是在电影和视频中,则至少需要每秒 60 帧的速度才可以使人的大脑轻松感知到流畅地运动。

c9b836d7b2bc564dca25a2433f430f4c.gif帧数不同的感觉

因此,就有了下面这个公式:1000ms / 60 frames = 16.666 ms/frame

我们需要在 16.66 毫秒内完成整个帧的计算,布局和渲染,否则不流畅,就需要掏出我们的 24K 合金双摄眼,找到优化点,让应用保持流畅。

定位优化方向

那么,当我们想优化 Flutter 应用的渲染性能时,入口点在哪里。Flutter 应用的每一帧都由框架层和引擎层互相协作完成。

最初,某些外部事件(如手势,网络等)或者异步任务会导致屏幕更新,该消息消息页会通知到引擎层。Flutter 框架层会拦截了该请求,执行 Tickers 相关的任务(如动画)。

这些任务也可能会重新发出一个请求,以供以后的帧渲染。(如动画暂停后再继续,需要在以后的阶段接收另一个 Begin 帧)。

然后,引擎层就可以开始做屏幕渲染工作了,但在开始之前,Flutter 框架依然会拦截该请求,并根据当前的组件结构和尺寸大小计算出更新布局、绘制相关的所有数据。

完成这些任务后,如果最终确定真的要在屏幕上绘制一些东西,它就会将需要渲染的新数据发送到 Flutter Engine,做最终的屏幕更新。

整个过程都在 Flutter 的 UI 线程中运行,如若阻塞,就会卡顿。

通常,应用开发者不需要关心引擎层的逻辑,但并不意味着我们不需要关心渲染性能。

引擎层的功能其实也是单一的,他只是拿到框架层的数据去做渲染而已。但是框架层是由我们控制的,我们所写的每一个组件都在框架层之上。

如何将传递给引擎层的更新数据做到最优,就是渲染优化时我们需要考虑的问题。

这些更新数据就是由 Flutter 中重要的三棵树生成的,建议不熟悉的读者去回看之前的这篇文章。

我们需要做的就是让 Flutter 中重建组件的个数尽量少。所以我说,优秀的 Flutter 代码可以在每次帧渲染的过程更新数据几乎为 0,这也是最优情况。

而最差的情况就是,每次帧渲染时要渲染整个应用的所有组件,这其中性能差异巨大。

在实际开发过程中,如果将整个页面写在一个单独的 StatefulWidget 中,那么每次状态更新时都会导致很多不必要的 UI 重建。

因此, 我们要学会拆解组件,使用良好设计模式和状态管理方案,当需要更新状态时将影响范围降到最小。

测定方法

在 Android Studio 中,找到 Flutter Performance (View > Tool Windows > Flutter Performance),就可以直接看到正在重建的 widget 数量。

这里,勾选 Show widget rebuild information 复选框,此功能也能够帮助你检测帧的渲染和显示时间是否超过 16ms。

b8b071f248c6e92e344504d71ad819db.pngFlutter performance window

在其他如 VSCode 编辑器中也可以使用 DevTool 做性能分析。

a76f94012c354b9c53f5bdec914126a7.pngDart DevTools ScreensDevTool

https://flutter.cn/docs/development/tools/devtools/overview

优化方法

合理使用const关键词

const 关键字可以通过附加到 Widget 的构造函数中,来抑制 Widget 的重建(与 Widget 缓存的状态相同)。

构建组件时使用 const 关键词,可以抑制 widget 的重建。

如下这个 Flutter 的默认计数器应用示例:import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget{

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Flutter Demo',

theme: ThemeData(

primarySwatch: Colors.blue,

),

home: MyHomePage(title: 'Flutter Demo Home Page'),

);

}

}

class MyHomePage extends StatefulWidget{

MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override

_MyHomePageState createState() => _MyHomePageState();

}

class _MyHomePageState extends State{

int _counter = 0;

void _incrementCounter() {

setState(() {

_counter++;

});

}

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text(widget.title),

),

body: Center(

child: Column(

mainAxisAlignment: MainAxisAlignment.center,

children: [

Text(

'You have pushed the button this many times:',

),

Text(

'$_counter',

style: Theme.of(context).textTheme.display1,

),

],

),

),

floatingActionButton: FloatingActionButton(

onPressed: _incrementCounter,

tooltip: 'Increment',

child: Icon(Icons.add),

), // This trailing comma makes auto-formatting nicer for build methods.

);

}

}

在不修改代码的默认情况下,widget 重建情况如下:

9be3b9e10c9fbde3c4830a04e2d0ffd1.gif未使用 const 的 widget

修改如下部分代码:-            Text(

-              'You have pushed the button this many times:',

-            ),

+            const Text(

+              'You have pushed the button this many times:',

+            ),

结果:

9be3b9e10c9fbde3c4830a04e2d0ffd1.gif

使用 const 的 widget

const 在 Dart 中用于声明常量,应用到 widget 中就相当于告诉 Flutter,“我这个组件不会随状态更新而改变了。”,因此达到了减少重建的效果。

使用 const 也需要注意如下几点:当const 修饰类的构造函数时,它要求该类的所有成员都必须是final的。

const 变量只能在定义的时候初始化。

合理利用 const 关键词,可以在很大程度上优化应用的性能,国外开发者 Crizant Lai 给出一了一份详细的数据。

如下示例:

b63372f211fd2935329b12df1a2d9b9b.png

这里,他使用 AnimatedPositioned 做动画组件,内部放一个展示 Flutter Logo 的 Image widget,分别使用 const 和 不使用 const 修饰 Image:const Image(

width: 100,

height: 100,

image: AssetImage('assets/logo.png'),

)

最终测试性能如下:

63a36e7b931ae1a9065539c53f1dbc4e.pngFPS

dda5ac4c0bb0c2645f2194c60fa069f3.png内存Crizant Lai 的原文:

https://crizantlai.medium.com/flutter-performance-analysis-of-const-constructor-d2a72fd8a043

合理使用组件

Flutter 实现的一些效果背后可能会使用 saveLayer() 这个代价很大的方法。为什么 saveLayer 代价大?

调用 saveLayer() 会开辟一片离屏缓冲区。将内容绘制到离屏缓冲区可能会触发渲染目标切换,这些切换在较早期的 GPU 中特别慢。

——来自 flutter.cn,https://flutter.cn/docs/testing/best-practices

如下这几个组件,底层都会触发 saveLayer() 的调用,同样也都会导致性能的损耗:ShaderMask

ColorFilter

Chip,当 disabledColorAlpha != 0xff 的时候,会调用saveLayer()。

Text,如果有 overflowShader,可能调用 saveLayer() ,

官方也给了我们一些非常需要注意的优化点:由于 Opacity 会使用屏幕外缓冲区直接使目标组件中不透明,因此能不用 Opacity Widget,就尽量不要用。有关将透明度直接应用于图像的示例,请参见 Transparent image,比使用 Opacity widget 更快,性能更好。

要在图像中实现淡入淡出,请考虑使用 FadeInImage 小部件,该小部件使用 GPU 的片段着色器应用渐变不透明度。

很多场景下,我们确实没必要直接使用 Opacity 改变透明度,如要作用于一个图片的时候可以直接使用透明的图片,或者直接使用 Container:Container(color: Color.fromRGBO(255, 0, 0, 0.5))

Clipping 不会调用 saveLayer()(除非明确使用 Clip.antiAliasWithSaveLayer),因此这些操作没有 Opacity 那么耗时,但仍然很耗时,所以请谨慎使用。

要创建带圆角的矩形,而不是应用剪切矩形,请考虑使用很多 widget 都提供的 borderRadius属性。

管理着色器编译垃圾

有时候,应用中的动画首次运行时会看起来非常卡顿,但是运行多次之后便可以正常运行,这可能就是由于着色器编译混乱导致的。

在图形渲染,着色器相当于是在 GPU 运行的一组代码。想要达到 60fps,需要在 16 毫秒内绘制一个平滑的帧,但是在编译着色器时,它花费的时间可能比应该花费的时间更多,可能会接近几百毫秒,并且会导致丢失数十个帧,将 fps 从 60 降至 6。

解决方法

Flutter 1.20 之后,Flutter 为开发者提供了非常方便的一组命令行工具,由此开发人员可以使用 Skia Shader Language 格式收集最终用户可能需要的着色器, 一旦将 SkSL 着色器打包到应用程序中,当用户打开应用程序时,就会自动进行预编译。

运行应用,添加 --cache-sksl 参数捕获 SkSL 中的着色器:flutter run --profile --cache-sksl

如果该应用已经运行,且没有带有 --cache-sksl 参数,则还需要 --purge-persistent-cache:flutter run --profile --cache-sksl --purge-persistent-cache

该参数可能会删除 SkSL 着色器捕获的较旧的非 SkSL 着色器缓存,因此只能在第一次运行时使用 --cache-sksl。

在不同平台上,可以执行以下命令,使用 SkSL 预热功能构建应用程序:

安卓flutter build apk — bundle-sksl-path flutter_01.sksl.json

iosflutter build ios --bundle-sksl-path flutter_01.sksl.json

结论

本文从原理到应用,分析了 Flutter 应用性能优化的原理、入口以及方法,但关于性能优化的话题还远不止于此,诸如状态管理方案的选择、动画的使用都是我们在实际开发过程中应当注意的优化方向。

本文结束,感谢阅读。如果你发现文章中有任何可以改进的地方,请直接联系我,我很乐意继续完善下去。

如果本文对你所有帮助,欢迎「点赞」和「在看」!

关于框架层更多内容,欢迎大家阅读我的新书《Flutter 开发之旅从南到北》。后台回覆「加群」,进入技术群。

方案是为解决特定问题或达成特定目标而制定的一系列计划或步骤。它的作用是提供一种系统性的方法,以有效地应对挑战、优化流程或实现目标。以下是方案的主要作用: 问题解决: 方案的核心目标是解决问题。通过系统性的规划和执行,方案能够分析问题的根本原因,提供可行的解决方案,并引导实施过程,确保问题得到合理解决。 目标达成: 方案通常与明确的目标相关联,它提供了一种达成这些目标的计划。无论是企业战略、项目管理还是个人发展,方案的制定都有助于明确目标并提供达成目标的路径。 资源优化: 方案在设计考虑了可用资源,以最大化其效用。通过明智的资源分配,方案可以在有限的资源条件下实现最大的效益,提高效率并减少浪费。 风险管理: 方案通常会对潜在的风险进行评估,并制定相应的风险管理策略。这有助于减轻潜在问题的影响,提高方案的可行性和可持续性。 决策支持: 方案提供了决策者所需的信息和数据,以便做出明智的决策。这种数据驱动的方法有助于减少不确定性,提高决策的准确性。 团队协作: 复杂的问题通常需要多个人的协同努力。方案提供了一个共同的框架,帮助团队成员理解各自的职责和任务,促进协作并确保整个团队朝着共同的目标努力。 监控与评估: 方案通常包括监控和评估的机制,以确保实施的有效性。通过定期的评估,可以及调整方案,以适应变化的环境或新的挑战。 总体而言,方案的作用在于提供一种有序、有计划的方法,以解决问题、实现目标,并在实施过程中最大化资源利用和风险管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值