明明用了const 但是devtool里相同参数的Widget还是有多个实例?

131 阅读3分钟

记录一个Flutter const 和devtool的有趣现象

奇怪,为什么我明明用了const 但是devtool里相同参数的Widget还是有多个实例

先说结论:

flutter profile或者debug 下 track-widget-creation会生效,会记录Widget声明的代码位置,所以devtool下显示的 Widget 实例个数是还未优化后的个数,实际上 const widget在打包后相同参数的会被优化成同一个实例。

相关链接

github.com/flutter/flu…

现在开始讲故事(不想看的可以关掉了,后面的内容没啥用):

某天,一个正在等待发版的Flutter开发,心血来潮地想优化一下APP内存占用,于是他打开了的devtool,然后天就塌了,为什么在闪屏页就有一个组件创建了500多个实例。

image.png 他快速扫了一遍代码,发现这是一个设置间距的Widget,难道是因为加了const?但也不至于会有这么多吧(APP开发UI有规范,间距都是固定的,就那么几个)?难道const对Widget无效? 赶紧写个demo验证一下。

(为什么感觉天塌了呢,不懂的可以先看一下大喵老师的这篇文章 Flutter 小技巧之为什么推荐 Widget 使用 const

先写一个Gap组件

class Gap extends StatelessWidget {
  final double height;

  const Gap({super.key, required this.height});

  @override
  Widget build(BuildContext context) {
    return SizedBox(width: height);
  }
}
class CounterExample extends StatefulWidget {
  const CounterExample({super.key});

  @override
  State<CounterExample> createState() => _CounterExampleState();
}

class _CounterExampleState extends State<CounterExample> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          const Gap(height: 1),
          const Gap(height: 1),
          Column(
            children: [
              const Gap(height: 1),
            ],
          )
        ],
      ),
    );
  }
}

按理论知识 这里应该是只一个Gap实例,运行一下看看结果(文章内运行模式都是Profile,防止JIT带来的影响,具体可以看一下关于Flutter AOT和JIT的文章)。

image.png 3个,看起来和项目的结果一样,再质疑一下自己,是不是Profile模式下,const优化没有效果?写个非Widget类试试看。

class GapSize {
  final double h;

  const GapSize(this.h);
}
class CounterExample extends StatefulWidget {
  const CounterExample({super.key});

  @override
  State<CounterExample> createState() => _CounterExampleState();
}

class _CounterExampleState extends State<CounterExample> {
  final gapSize1 = const GapSize(1);
  final gapSize2 = const GapSize(1);
  final gapSize3 = const GapSize(1);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          SizedBox(height: gapSize1.h),
          SizedBox(height: gapSize2.h),
          SizedBox(height: gapSize3.h),
          const Gap(height: 1),
          const Gap(height: 1),
          Column(
            children: [
              const Gap(height: 1),
            ],
          )
        ],
      ),
    );
  }
}

image.png Gap有3个,GapSize就1个,说明const在Profile模式下应该是生效的,难道是Widget有什么特殊处理?打印一下实例看看。

Gap:

image.png

GapSize:

image.png

同样是实例,Gap多了一些关于组件创建位置的属性,难道是因为他是个组件,所以与众不同?但Widget不是只是一个配置文件吗,实际上工作的应该是Element 和 RenderObject,配置相同的Widget难道不可以合并成一个Widget?是因为在不同地方定义了吗,但GapSize定位行数也不同,为什么就只有一个呢,好烦 CPU要烧了。。。 感觉自己对const的理解好像被颠覆了。 赶紧把问题抛给群里的大佬。 经过一些讨论,有人找出了一条issue,github.com/flutter/flu…

19年就有人提出了,原来是因为track-widget-creation,那也就能解释的通为什么Gap的实例打印出来多了创建位置的信息了,顺便来看看 no-track-widget-creation的结果。

flutter run --profile --no-track-widget-creation

image.png

哎!正常了,符合理论知识,安心了,下班了。

顺提留下一个另一个问题, 关于flutter command对track-widget-creation的描述,看起来只在Debug模式下有效,但是实测Profile似乎也有效果,真是神奇。

6c0f05e9f2dacab38485ae79be92b8ed.png

估计文章内容里面有很多不正确的地方,希望大家帮忙指出。