提醒:不要在 Flutter widget 生命周期的早期(build()
或 initState()
)里直接做耗时/阻塞操作。
🔎 背景:为什么 build
/ initState
不能阻塞?
- Flutter 的渲染是单线程事件驱动(Main Isolate)。
build()
调用非常频繁(每次setState()
都可能触发),要求 极快执行。initState()
是组件初始化的生命周期起点,此时也在 UI 线程里,如果你这里卡住,UI 会"白屏"或掉帧。
所以:
- 在这两个地方直接
await
耗时任务 (比如数据库查询 / 硬盘 IO / 网络请求),就会导致 UI 主线程阻塞。 - 表现 → "界面迟迟不显示"、"UI卡住动画不动"。
✅ 合理做法
1. 用 延迟到下一帧的机制 (addPostFrameCallback
)
dart
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await loadAsyncStuff(); // 在 build 绘制到屏幕之后再执行
});
}
这样能保证 先把 UI 渲染出来(哪怕是空壳子 Skeleton),再执行耗时逻辑 → 用户体验明显好。
2. 用 FutureBuilder / StreamBuilder
dart
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Item>>(
future: _loadItems(), // 异步加载
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return ListView(...);
},
);
}
优点:
- UI 与数据的生命周期解耦,异步数据返回前 UI 也能安全显示 loading。
- 不会阻塞 build。
3. 提前放到 外部 Provider/Bloc 里做
很多时候,widget 不应该承担数据加载逻辑 → 交给 Provider/Bloc:
- initState 里只 dispatch 一个事件
context.read<MyBloc>().add(LoadData());
- 真正的数据拉取在 Bloc/Repository 层运行 → widget 不会阻塞。
4. 避免同步 IO
- Dart 的某些 IO API(如 File.readAsBytesSync)是同步阻塞的 → 千万不要放在 build/initState。
- 一定要用 async 版本 (
readAsBytes
)。
📌 实际应用到 storeAssets
的场景
现在 storeAssets
做了 本地 IO + 网络下载 (耗时极长),如果在 initState
里直接调用:
dart
@override
void initState() {
super.initState();
storeAssets(actions, (progress) {
setState(() { ... });
});
}
⚠️ 问题:UI 会在 storeAssets
第一个 await 卡住(黑屏、假死)。
优化:
dart
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// 这里 UI 已经渲染出来了
_loadAssets();
});
}
Future<void> _loadAssets() async {
await storeAssets(actions, (progress) {
setState(() => _progress = progress);
});
}
这样好处:
- 界面能立刻出来(比如显示"正在准备下载")。
- 下载在后台执行,进度条随时刷新。
🎯 总结最佳实践
- 不要 在
build
/initState
里直接await
耗时操作。 - 用 addPostFrameCallback:保证渲染优先。
- 或者 FutureBuilder/StreamBuilder:UI → 数据异步绑定。
- 对于复杂业务 → 用状态管理工具 (Provider/Bloc/Cubit),UI 只
listen
。