记录一个Flutter const 和devtool的有趣现象
奇怪,为什么我明明用了const 但是devtool里相同参数的Widget还是有多个实例
先说结论:
flutter profile或者debug 下 track-widget-creation会生效,会记录Widget声明的代码位置,所以devtool下显示的 Widget 实例个数是还未优化后的个数,实际上 const widget在打包后相同参数的会被优化成同一个实例。
相关链接
现在开始讲故事(不想看的可以关掉了,后面的内容没啥用):
某天,一个正在等待发版的Flutter开发,心血来潮地想优化一下APP内存占用,于是他打开了的devtool,然后天就塌了,为什么在闪屏页就有一个组件创建了500多个实例。
他快速扫了一遍代码,发现这是一个设置间距的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的文章)。
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),
],
)
],
),
);
}
}
Gap有3个,GapSize就1个,说明const在Profile模式下应该是生效的,难道是Widget有什么特殊处理?打印一下实例看看。
Gap:
GapSize:
同样是实例,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
哎!正常了,符合理论知识,安心了,下班了。
顺提留下一个另一个问题, 关于flutter command对track-widget-creation的描述,看起来只在Debug模式下有效,但是实测Profile似乎也有效果,真是神奇。
估计文章内容里面有很多不正确的地方,希望大家帮忙指出。