Const在Flutter性能方面的表现|社区征文

社区征文

在实际的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 constconst
constnot.gifconst.gif
no constconst

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。

134
3
0
0
关于作者
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论