Flutter 性能优化:构建、列表与 Isolate
hardflutterperformancedevtoolsprofilerisolatecomputelistviewrepaint-boundaryimage-cache
性能分析工具
第一原则:先测量,再优化。 不要凭感觉猜测瓶颈。
# profile 模式运行(不用 debug,开 JIT 优化但保留 DevTools)
flutter run --profile
Flutter DevTools 关键面板:
- Performance:帧时间线,查找哪一帧超过 16ms(60fps)或 8ms(120fps)
- Widget Inspector:查看 Widget 树,debug 重建次数(点击 bug 图标开启
debugRepaintRainbow) - Memory:内存分配,查找泄漏
- CPU Profiler:火焰图,定位 CPU 热点
减少不必要的 rebuild
精确控制 BlocBuilder / Selector
// ❌ 整个 UserState 任何字段变化都重建
BlocBuilder<UserBloc, UserState>(
builder: (_, state) => UserAvatar(url: state.avatarUrl),
)
// ✅ 只有 avatarUrl 变化时才重建
BlocSelector<UserBloc, UserState, String>(
selector: (state) => state.avatarUrl,
builder: (_, avatarUrl) => UserAvatar(url: avatarUrl),
)
RepaintBoundary —— 隔离重绘区域
// 动画区域和静态区域放在不同 RepaintBoundary
// 动画重绘时不会触发外层 Widget 重绘
RepaintBoundary(
child: AnimatedLoadingSpinner(), // 频繁重绘的动画
)
避免在 build 中创建对象
// ❌ 每次 build 都创建新对象(GC 压力)
Widget build(BuildContext context) {
return TextStyle(color: Theme.of(context).primaryColor, fontSize: 16);
}
// ✅ 用 const 或 cache
static const _style = TextStyle(fontSize: 16);
Widget build(BuildContext context) {
return Text('Hello', style: _style.copyWith(color: Theme.of(context).primaryColor));
}
列表性能优化
ListView.builder vs ListView
// ❌ ListView:一次性创建所有子项(item 多时性能差)
ListView(children: items.map((e) => ItemCard(e)).toList())
// ✅ ListView.builder:懒加载,仅创建可见 + 缓存区间的 item
ListView.builder(
itemCount: items.length,
// cacheExtent:预加载区域(像素),默认 250
cacheExtent: 500,
// addRepaintBoundaries:默认 true,每个 item 自动加 RepaintBoundary
itemBuilder: (_, index) => ItemCard(items[index]),
)
const Item Widget
// ❌ 每次 build 都创建新 ItemCard 实例
ItemCard(item: items[index])
// ✅ 如果 item 不变,加 const(需要 item 本身是 const)
// 实践中配合 equatable 实现 == 判断
图片加载优化
// ExtendedImage / CachedNetworkImage:磁盘缓存 + 内存缓存
CachedNetworkImage(
imageUrl: item.imageUrl,
memCacheWidth: 200, // 按需降低内存分辨率
fadeInDuration: const Duration(milliseconds: 200),
placeholder: (_, __) => const ShimmerPlaceholder(),
errorWidget: (_, __, ___) => const Icon(Icons.broken_image),
)
// 限制内存缓存大小
PaintingBinding.instance.imageCache.maximumSizeBytes = 100 << 20; // 100MB
Isolate / compute —— 后台线程
Dart 是单线程的,CPU 密集型任务必须移到 Isolate,否则 UI 卡顿:
// compute:简单包装,适合一次性任务
Future<List<Product>> parseProducts(String json) async {
// 在 Isolate 中解析大 JSON,不阻塞 UI
return await compute(_parseProductsIsolate, json);
}
// 必须是顶层函数或 static 方法
List<Product> _parseProductsIsolate(String json) {
return (jsonDecode(json) as List).map((e) => Product.fromJson(e)).toList();
}
// Isolate.spawn:更灵活,适合长时间或多次通信
void _heavyTask(SendPort sendPort) {
// 在新 Isolate 执行
final result = computeHeavyWork();
sendPort.send(result);
}
Future<int> runHeavyTask() async {
final receivePort = ReceivePort();
await Isolate.spawn(_heavyTask, receivePort.sendPort);
return await receivePort.first as int;
}
注意:Isolate 之间不共享内存,对象传递通过序列化复制(除非使用 TransferableTypedData)。
内存优化
图片内存
// 大图缩放后缓存(避免加载原始大图)
image.Image resized = image.copyResize(img, width: 800);
// 使用 ResizeImage 限制内存中的分辨率
Image(
image: ResizeImage(
NetworkImage(url),
width: 400,
height: 400,
),
)
dispose 资源
class _MyPageState extends State<MyPage> {
late final TextEditingController _ctrl;
late final AnimationController _animation;
late final StreamSubscription _sub;
@override
void initState() {
super.initState();
_ctrl = TextEditingController();
_animation = AnimationController(vsync: this, duration: const Duration(seconds: 1));
_sub = stream.listen((_) {});
}
@override
void dispose() {
_ctrl.dispose(); // ✅ 必须 dispose
_animation.dispose(); // ✅
_sub.cancel(); // ✅
super.dispose();
}
}
弱引用缓存
// Expando:类似 WeakHashMap,key 被 GC 回收后 value 自动清理
final _cache = Expando<ComputedResult>();
ComputedResult getResult(Widget key) {
return _cache[key] ??= ComputedResult.compute(key);
}
常见性能指标
| 问题 | 表现 | 工具定位 |
|---|---|---|
| Jank(掉帧) | 滑动不流畅 | Performance 帧时间线 |
| 过度 build | CPU 高,build 频繁 | Widget Inspector repaint |
| 内存泄漏 | 内存持续增长 | Memory 快照对比 |
| 图片内存大 | OOM | Memory → Image cache |
| 主线程阻塞 | UI 冻结 | CPU Profiler 火焰图 |