性能优化
Flutter 应用的性能优化主要围绕 减少不必要的重建 和 减少不必要的计算 两个核心原则展开。
优化总览
| 优化方向 | 核心思路 | 影响程度 |
|---|---|---|
| 减少重建 | 只重建变化的部分 | ★★★ |
| 懒加载 | 用到时才创建 | ★★★ |
| 图片优化 | 合理使用缓存和尺寸 | ★★☆ |
| 列表优化 | 复用列表项 | ★★☆ |
| 避免阻塞 | 耗时操作放异步 | ★★☆ |
1. 减少不必要的 Widget 重建
这是 Flutter 性能优化中最重要的一条规则。
使用 const 构造函数
dart
// ❌ 每次父组件重建时,Text 也会重建
Text('Hello')
// ✅ 编译时常量,不会重建
const Text('Hello')dart
// ❌ 列表项没有 const
ListView(
children: [
Text('Item 1'),
Text('Item 2'),
Text('Item 3'),
],
)
// ✅ 列表项使用 const
ListView(
children: const [
Text('Item 1'),
Text('Item 2'),
Text('Item 3'),
],
)拆分 Widget,缩小重建范围
dart
// ❌ 整个页面重建,即使只有计数器变化
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final count = context.watch<Counter>().count;
return Scaffold(
appBar: AppBar(title: const Text('Counter')), // 不需要重建
body: Center(child: Text('$count')), // 需要重建
bottomNavigationBar: BottomAppBar(child: ...), // 不需要重建
);
}
}
// ✅ 只有计数器部分重建
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: const _CounterBody(), // 独立 Widget,只有它监听变化
bottomNavigationBar: BottomAppBar(child: ...),
);
}
}
class _CounterBody extends StatelessWidget {
const _CounterBody();
@override
Widget build(BuildContext context) {
final count = context.watch<Counter>().count; // 只在这里监听
return Center(child: Text('$count'));
}
}使用 Selector 精确监听
dart
// ❌ 整个 User 对象变化时都重建,即使只用到了 name
context.watch<User>();
// ✅ 只有 name 变化时才重建
context.select<User, String>((user) => user.name);2. 列表优化
使用 ListView.builder
dart
// ❌ 一次性创建所有子项,数据量大时卡顿
ListView(
children: List.generate(1000, (i) => Text('Item $i')),
)
// ✅ 懒加载,只创建可见项
ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) => Text('Item $index'),
)给列表项添加 Key
dart
// ✅ 列表项有唯一 Key,Flutter 可以高效复用
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
key: ValueKey(item.id), // 唯一标识
title: Text(item.name),
);
},
)避免在 itemBuilder 中创建对象
dart
// ❌ 每次滚动都新建 Color 和 TextStyle
itemBuilder: (context, index) {
return Text(
'Item $index',
style: TextStyle(color: Colors.red, fontSize: 16), // 每次新建
);
}
// ✅ 提取为常量或成员变量
static const _itemStyle = TextStyle(color: Colors.red, fontSize: 16);
itemBuilder: (context, index) {
return Text('Item $index', style: _itemStyle);
}3. 图片优化
dart
// ❌ 直接用 Image.network,无缓存、无占位
Image.network('https://example.com/large-photo.jpg')
// ✅ 使用 cached_network_image
CachedNetworkImage(
imageUrl: 'https://example.com/large-photo.jpg',
placeholder: (_, __) => const CircularProgressIndicator(),
errorWidget: (_, __, ___) => const Icon(Icons.error),
memCacheWidth: 300, // 限制内存缓存尺寸(像素),避免加载原始大图
)图片优化要点
- 使用
memCacheWidth/memCacheHeight:加载 4000x3000 的原图到 300 宽的容器中是浪费内存,限制缓存尺寸可以大幅降低内存占用 - 使用 WebP 格式:比 PNG/JPG 体积更小
- 提供缩略图:列表中使用小图,详情页使用大图
4. 避免阻塞主线程
dart
// ❌ 在主线程做耗时 JSON 解析
void loadData() async {
final response = await dio.get('/large-data');
final data = jsonDecode(response.data); // 大数据时可能卡顿
setState(() => _data = data);
}
// ✅ 耗时计算放到 Isolate
void loadData() async {
final response = await dio.get('/large-data');
final data = await compute(jsonDecode, response.data); // 在后台线程解析
setState(() => _data = data);
}何时使用 Isolate?
- JSON 数据超过 100KB 时
- 图片处理(裁剪、滤镜)
- 加密/解密
- 复杂排序/计算
一般网络请求、UI 操作不需要 Isolate。
5. 懒加载
页面懒加载
dart
// ❌ 一次性加载所有 Tab 页面
TabBarView(
children: [
HomePage(), // 立即创建
SearchPage(), // 立即创建
ProfilePage(), // 立即创建
],
)
// ✅ 使用 LazyLoad 或只在可见时加载
TabBarView(
children: [
HomePage(),
SearchPage(),
ProfilePage(),
],
)
// TabBarView 默认是懒加载的,但会预缓存相邻页面
// 可通过 PageController 设置 cacheExtent 控制预缓存范围图片懒加载
dart
// ✅ 列表中的图片只在即将可见时加载
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return CachedNetworkImage(
imageUrl: items[index].imageUrl,
// cached_network_image 自动实现懒加载
);
},
)6. 资源释放
dart
// ❌ 忘记释放 Controller
class _MyState extends State<MyWidget> {
final _controller = TextEditingController();
// 没有 dispose!→ 内存泄漏
}
// ✅ 在 dispose 中释放所有资源
class _MyState extends State<MyWidget> {
late final TextEditingController _controller;
late final ScrollController _scrollController;
StreamSubscription? _subscription;
@override
void initState() {
super.initState();
_controller = TextEditingController();
_scrollController = ScrollController();
_subscription = someStream.listen((_) {});
}
@override
void dispose() {
_controller.dispose();
_scrollController.dispose();
_subscription?.cancel();
super.dispose();
}
}性能分析工具
Flutter DevTools
bash
# 启动 DevTools
flutter pub global activate devtools
flutter pub global run devtools
# 或在运行应用时按终端提示的链接打开
flutter run常用分析面板
| 面板 | 用途 |
|---|---|
| Widget Inspector | 查看 Widget 树,检查布局问题 |
| Performance | 分析帧率,找出卡顿原因 |
| CPU Profiler | 分析 CPU 使用,找出耗时函数 |
| Memory | 分析内存使用,检测内存泄漏 |
| Network | 查看网络请求 |
检查重建次数
dart
// 在 Widget 中添加 debugPrint,观察重建频率
@override
Widget build(BuildContext context) {
debugPrint('${widget.runtimeType} rebuilt'); // 开发时使用
return ...;
}提示
debugPrint 仅在 Debug 模式有效,Release 模式会自动移除。上线前记得删除或改用日志框架。
性能优化检查清单
- [ ] 可用
const的地方都用了const - [ ] 长列表使用
ListView.builder而非ListView(children: [...]) - [ ] 图片使用
CachedNetworkImage并设置memCacheWidth - [ ] 耗时操作使用
compute放到 Isolate - [ ] Controller、Subscription 在
dispose中释放 - [ ]
context.watch精确到最小范围 - [ ] 列表项有唯一
key - [ ] 使用 DevTools 验证无内存泄漏
