在实际的Flutter开发中,可以发现编辑器AS会提示在组件之前加上const关键字,
这是因为Flutter2之后,多了一个linter规则,prefer_const_constructors,官方建议首选使用const来实例化常量构造函数。
那const作用是什么?并且在性能方面对整个app有多大的提升?
一、Const的作用
const 是 constant 的缩写,本意是不变的,不易改变的意思,包括C++、go中都有此关键字,同样的,在Flutter中也是表示不变的意思。具体来看看下面的代码。
Row(
children: [
Image(image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg')),
Text("$_counter")
],
);
这是一个水平布局,内部排列了一个Image和Text,注意这个Text的是有一个动态的值_counter。
为了能够更新_counter,必然要调用setState() 方法。我们都知道,如果调用setState() ,那么整个Row包括Image和Text都会自动递归重建。每调用一次,父widget和子widget都会重建一次,那么在复杂的UI和业务场景下,就加深了app的不稳定性。
这就是为什么在开发中,要尽量在小的范围去使用setState,避免不必要的重建任务。为了优化这个问题,官方就更新出了const关键字,被const修饰的widget,就代表永远不会被重建。
比如在上述代码中Image是不可变的,Text是可变的,那么在Image之间加上const修饰,当调用setState() 时,只会更新Text,Image不会被重新构建。
Row(
children: [
const Image(image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg')),
Text("$_counter")
],
);
二、性能分析
2.1 widget rebuild状态
DevTools提供了一个查询widget rebuild状态的工具,在 Widget rebuild stats 中勾选 Track widget rebuilds 来查看 widget 的重建信息。重建信息包括 Widget 名字、源码位置、上一帧中重建次数、当前页面中重建次数。
在每个widget之前都有一个小图标,
- 黄色旋转圆圈 - 重建次数过多
- 灰色圆圈 - 未重建
- 灰色旋转圆圈 - 重建
为了进行const对比,我们以上面代码为例,
Row(
children: [
const Image(image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg')),
Text("$_counter")
],
);
在Image前加上const,Text则不加,当调用setState时,观察两个widget的情况。
清楚的发现,没加const的Image widget前面的圆圈在旋转,则表示Image在重建,且重建次数+1。
2.2 内存占用
关于内存,DevTool同样提供了内存分析工具Memory,接下来结合案例进行分析。
在项目中新建两个类,内部不做额外的动作,
void _buildConstObject(){
const ConstObject();
}
void _buildConstObjectNot(){
ConstObjectNot();
}
其中ConstObject 加上const修饰,ConstObjectNot则不进行修饰,在触发build时,两个对象同时进行1000次的创建,
void _doBuild(){
for(var i = 0; i< 1000;i++){
_buildConstObject();
_buildConstObjectNot();
}
}
打开内存分析工具,可以发现未加Const修饰的ConstObjectNot创建了1000个对象,所占用内存约16k,而加了const的ConstObject则可以忽略不计。
注意这里ConstObjectNot和ConstObject内部是没有做任何widget创建的,如果在实际复杂的项目中,未使用const,内存将成倍增加。
2.3 流畅性
在DevTool中打开performance overlay, 在app顶部就会出现性能图层,这两张图表显示的是应用的耗时信息。如果 UI 产生了卡顿(跳帧),这些图表可以帮助分析应用中卡顿,每一张图表都代表当前线程的最近 300 帧表现。
如上图,第一张图属于raster 线程的性能情况即GPU性能,第二张图显示的UI线程性能表现。
当中垂直的绿色条条代表的是当前帧。每一帧都应该在 1/60 秒(大约 16 ms)内创建并显示。如果有一帧超时(任意图像)而无法显示,就导致了卡顿,图表之一就会展示出来一个红色竖条。如果是在 UI 图表出现了红色竖条,则表明 Dart 代码消耗了大量资源。而如果红色竖条是在 GPU 图表出现的,意味着场景太复杂导致无法快速渲染。
为了验证流畅性,我们开启了一个动画,动画在规定时间内进行重复性的放大缩小动作,且分为两个场景,一个场景是在所有widget以及对象前加上const修饰,另外一个场景则什么都不做,对比查看每帧的耗时。
class AnLogo extends AnimatedWidget {
static final _opacityTween = Tween<double>(begin: 0.1, end: 1.0);
static final _sizeTween = Tween<double>(begin: 0.0, end: 300.0);
const AnLogo({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
Animation<double> animation1 = listenable as Animation<double>;
return Scaffold(
appBar: AppBar(
title: const Text("动画"),
),
body: Center(
child: Opacity(
opacity: _opacityTween.evaluate(animation1),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
height: _sizeTween.evaluate(animation1),
width: _sizeTween.evaluate(animation1),
child: Image.asset("images/ic_1.jpeg"),
),
),
),
);
}
}
no const | const |
---|---|
no const | const |
---|---|
GPU帧率:
GPU | |
---|---|
no const平均最大耗时/帧 | 9.9ms/frame |
const平均最大耗时/帧 | 7.6ms/frame |
UI线程帧率:
UI线程 | |
---|---|
no const平均最大耗时/帧 | 7.8ms/frame |
const平均最大耗时/帧 | 7.1ms/frame |
从实验结果上看,没有加const的GPU帧率平均最大达到9.9ms/帧,而加了const的GPU帧率比之降低了约2.3ms;UI帧率(CPU)加const与不加const相差不大,约0.7ms。
三、总结
从上面的测试看,不管是内存占用还是流畅性,添加const修饰的性能都是优于未添加const修饰的性能,const减少了组件的重建以及对象的创建,很有必要在合适的时机去使用const。