Flutter: 性能优化
性能是体验的底线——60fps 是承诺,59fps 是失败。
📋 前置知识:需要先掌握以下内容
- 第10课:列表与滚动
- 第12课:状态管理 — Riverpod
1. 你将学到
- Flutter DevTools:Performance Overlay / Widget Rebuild Tracker / CPU Profiler / Memory Chart
- 渲染优化:const 构造函数 / RepaintBoundary / ListView.builder 懒加载 / 避免大 Widget 重建
- 内存优化:图片缓存策略 / AutomaticKeepAliveClientMixin / Dispose 资源释放
- 网络优化:数据分页 / 图片 CDN / cached_network_image / 预加载
- ShopApp:千万商品列表 60fps 滚动优化方案
2. 一个卡顿的真实故事
(1) 痛点:商品列表 30fps 滚动
Bob 的 ShopApp 商品列表在 1000 项时滚动流畅,但到 10000 项时掉到 30fps。DevTools 显示:每个商品卡片在滚动时都触发 rebuild,因为父 Widget 的 setState 导致整棵子树重建。更糟的是,图片没有缓存,每次滚入视口都重新下载,内存占用飙升到 500MB。
(2) 精准优化的解法
性能优化不是"全优化",而是找到瓶颈精准优化。DevTools 定位问题 → RepaintBoundary 隔离 → const 减少重建 → 图片缓存减少网络。
DART
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
// ⚙️ 依赖:cached_network_image: ^3.0.0(添加到 dependencies)
// Product 来自 ShopApp 项目,完整定义见第26课
// Key optimizations
const ProductCard(...); // 1. const avoids rebuild
RepaintBoundary(child: Image.network(...)); // 2. Isolate repaint
CachedNetworkImage(imageUrl: ...); // 3. Cache images
AutomaticKeepAliveClientMixin; // 4. Keep tab state
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
(3) 收益:千万级 60fps + 内存减半
Bob 精准优化后,千万商品列表稳定 60fps,内存从 500MB 降到 200MB,图片缓存命中率 95%。
3. 性能优化全景
graph TD
PO[Performance Optimization] --> DEV[DevTools]
PO --> RO[Render Optimization]
PO --> MO[Memory Optimization]
PO --> NO[Network Optimization]
RO --> |const| CW[const Widgets]
RO --> |RepaintBoundary| RB2[Isolate Repaint]
MO --> |cache| IMG[Image Cache]
NO --> |pagination| PG[Pagination API]
DEV --> |Overlay| FPS[FPS Monitor]
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
4. DevTools 性能分析
(1) DevTools 核心工具
| 工具 | 用途 | 使用方式 |
|---|---|---|
| Performance Overlay | 实时 FPS + GPU 线程耗时 | debugShowPerformanceOverlay: true |
| Widget Rebuild Tracker | 追踪哪些 Widget 频繁重建 | DevTools → Rebuild Tracker |
| CPU Profiler | 分析 CPU 热点 | DevTools → CPU Profiler |
| Memory Chart | 内存分配与 GC | DevTools → Memory |
| Network Inspector | 网络请求瀑布图 | DevTools → Network |
| Flutter Inspector | Widget 树可视化 | DevTools → Inspector |
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:启用性能分析
DART
import 'package:flutter/material.dart';
// Development mode: enable performance overlay
MaterialApp(
debugShowPerformanceOverlay: true,
showSemanticsDebugger: false,
// ...
)
// Profile mode: accurate performance measurement
// Run: flutter run --profile
// Then open DevTools: flutter pub global run devtools
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
| 运行模式 | 优化 | 用途 |
|---|---|---|
| Debug | 无优化 | 开发调试 |
| Profile | 接近生产 | 性能分析 |
| Release | 完全优化 | 生产发布 |
5. 渲染优化
(1) const 构造函数
| 对比 | 非 const | const |
|---|---|---|
| 重建行为 | 每次创建新实例 | 复用同一实例 |
| canUpdate | 可能不通过 | 一定通过 |
| 性能影响 | 多次重建开销 | 零重建开销 |
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:const 优化商品卡片
DART
import 'package:flutter/material.dart';
// BAD: not const, rebuilds every time
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(8.0), // New instance each build
child: Text('Price: \$${product.price}'),
);
}
// GOOD: const where possible
Widget build(BuildContext context) {
return const Padding( // const padding
padding: EdgeInsets.all(8.0), // Compile-time constant
child: Text('Fixed text'), // const if content is constant
);
}
// For dynamic content, const the wrapper
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.all(8.0),
child: _DynamicPrice(product: product), // Can't const dynamic
);
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:RepaintBoundary 隔离重绘
DART
import 'package:flutter/material.dart';
// Product 来自 ShopApp 项目,完整定义见第26课
// Without RepaintBoundary: entire list repaints when one item changes
ListView.builder(
itemCount: 1000,
itemBuilder: (_, i) => ProductCard(product: products[i]),
)
// With RepaintBoundary: only changed card repaints
ListView.builder(
itemCount: 1000,
itemBuilder: (_, i) => RepaintBoundary(
key: ValueKey(products[i].id),
child: ProductCard(product: products[i]),
),
)
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
(2) 避免大 Widget 重建
| 问题 | 解法 |
|---|---|
| 大 build 方法整体重建 | 拆分为多个小 Widget |
| setState 重建整棵子树 | 只重建受影响的部分 |
| 父 Widget 变化影响子树 | 用 const 子组件阻断 |
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:拆分大 Widget
DART
import 'package:flutter/material.dart';
// BAD: entire page rebuilds on counter change
class BadPage extends StatefulWidget {
@override
State<BadPage> createState() => _BadPageState();
}
class _BadPageState extends State<BadPage> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(children: [
const HeavyHeader(), // Rebuilds unnecessarily!
const ProductGrid(), // Rebuilds unnecessarily!
Text('Count: $_count'), // Only this needs rebuild
ElevatedButton(onPressed: () => setState(() => _count++), child: const Text('+')),
]);
}
}
// GOOD: only counter part rebuilds
class GoodPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(children: [
const HeavyHeader(), // Never rebuilds (const)
const ProductGrid(), // Never rebuilds (const)
const _CounterSection(), // Only this rebuilds
]);
}
}
// HeavyHeader / ProductGrid 为示例占位 Widget
class HeavyHeader extends StatelessWidget {
const HeavyHeader({super.key});
@override Widget build(BuildContext context) => const Text('Header');
}
class ProductGrid extends StatelessWidget {
const ProductGrid({super.key});
@override Widget build(BuildContext context) => const Text('Grid');
}
class _CounterSection extends StatefulWidget {
const _CounterSection();
@override
State<_CounterSection> createState() => _CounterSectionState();
}
class _CounterSectionState extends State<_CounterSection> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(children: [
Text('Count: $_count'),
ElevatedButton(onPressed: () => setState(() => _count++), child: const Text('+')),
]);
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
6. 内存优化
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:AutomaticKeepAliveClientMixin
DART
import 'package:flutter/material.dart';
// Product / ProductTile 来自 ShopApp 项目,完整定义见第26/27课
// Keep tab content alive when switching tabs
class ProductListTab extends StatefulWidget {
@override
State<ProductListTab> createState() => _ProductListTabState();
}
class _ProductListTabState extends State<ProductListTab>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true; // Keep state when tab is hidden
@override
Widget build(BuildContext context) {
super.build(context); // Required for mixin
return ListView.builder(
itemCount: products.length,
itemBuilder: (_, i) => ProductTile(product: products[i]),
);
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:Dispose 资源释放
DART
import 'dart:async';
import 'package:flutter/material.dart';
// DetailPage / productStream 来自 ShopApp 项目
class _DetailPageState extends State<DetailPage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late TextEditingController _textController;
StreamSubscription? _subscription;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(seconds: 1));
_textController = TextEditingController();
_subscription = productStream.listen((_) => setState(() {}));
}
@override
void dispose() {
_controller.dispose(); // Release animation
_textController.dispose(); // Release text controller
_subscription?.cancel(); // Cancel stream
super.dispose();
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
7. 网络优化
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:cached_network_image 图片缓存
DART
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
// ⚙️ 依赖:cached_network_image: ^3.0.0(添加到 dependencies)
// product.imageUrl 来自 ShopApp Product 实体
CachedNetworkImage(
imageUrl: product.imageUrl,
placeholder: (context, url) => Container(
color: Colors.grey[200],
child: const Center(child: CircularProgressIndicator()),
),
errorWidget: (context, url, error) => const Icon(Icons.error),
fadeInDuration: const Duration(milliseconds: 300),
memCacheWidth: 400, // Resize in memory to save RAM
maxWidth: 800, // Disk cache max width
)
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:分页 API + 预加载
DART
import 'package:flutter/material.dart';
// ⚙️ 依赖:无额外依赖(Dart/Flutter 标准库即可)
// Product / ProductTile / LoadingIndicator 来自 ShopApp 项目
// api.fetchProducts 来自 ShopApp 网络层
class PaginatedProductList extends StatefulWidget {
@override
State<PaginatedProductList> createState() => _PaginatedProductListState();
}
class _PaginatedProductListState extends State<PaginatedProductList> {
final _controller = ScrollController();
List<Product> _products = [];
int _page = 1;
bool _hasMore = true;
@override
void initState() {
super.initState();
_loadPage();
_controller.addListener(() {
// Preload when 2 screens away from bottom
if (_controller.position.pixels >= _controller.position.maxScrollExtent * 0.8) {
_loadPage();
}
});
}
Future<void> _loadPage() async {
if (!_hasMore) return;
final newProducts = await api.fetchProducts(page: _page, limit: 20);
setState(() {
_products.addAll(newProducts);
_page++;
_hasMore = newProducts.length >= 20;
});
}
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: _controller,
itemCount: _products.length + (_hasMore ? 1 : 0),
itemExtent: 72, // Fixed height for performance
itemBuilder: (_, i) {
if (i == _products.length) return const LoadingIndicator();
return ProductTile(product: _products[i]);
},
);
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
8. 完整示例:ShopApp 高性能商品列表
DART
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:cached_network_image/cached_network_image.dart';
// ⚙️ 依赖:flutter_riverpod: ^2.0.0, cached_network_image: ^3.0.0
// Product / productProvider / cartProvider 来自 ShopApp 项目,完整定义见第26/27课
class OptimizedProductGrid extends ConsumerWidget {
const OptimizedProductGrid({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final productsAsync = ref.watch(productProvider);
return productsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => ErrorWidget(e),
data: (products) => GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 0.7,
),
itemCount: products.length,
itemBuilder: (_, i) => RepaintBoundary(
key: ValueKey(products[i].id),
child: _OptimizedProductCard(product: products[i]),
),
),
);
}
}
class _OptimizedProductCard extends ConsumerWidget {
final Product product;
const _OptimizedProductCard({required this.product});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
flex: 3,
child: RepaintBoundary( // Isolate image repaint
child: CachedNetworkImage(
imageUrl: product.imageUrl,
fit: BoxFit.cover,
memCacheWidth: 200, // Save memory
placeholder: (_, __) => Container(color: Colors.grey[200]),
errorWidget: (_, __, ___) => const Icon(Icons.image_not_supported),
),
),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product.name, maxLines: 1, overflow: TextOverflow.ellipsis),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('\$${product.price.toStringAsFixed(2)}',
style: const TextStyle(color: Colors.green)),
IconButton(
icon: const Icon(Icons.add_shopping_cart, size: 18),
onPressed: () => ref.read(cartProvider.notifier).addItem(product),
),
],
),
],
),
),
),
],
),
);
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
❓ 常见问题
Q 性能分析用 debug 还是 profile 模式?
A 必须用 profile 模式。debug 模式有大量检查代码,FPS 不准确。
Q const 能加就加吗?
A 是的。const 构造函数是零成本优化,只要构造函数参数都是编译期常量就可以加。
Q RepaintBoundary 加太多有坏处吗?
A 有。每个 RepaintBoundary 创建一个独立绘制层,增加内存。只在重绘频繁的隔离点添加。
Q itemExtent 为什么能提升性能?
A 固定项高度后 ListView 不需要测量每个子项,直接计算偏移量,滚动更流畅。
Q CachedNetworkImage 和 Image.network 区别?
A Image.network 无缓存每次下载;CachedNetworkImage 自动磁盘+内存缓存,支持占位图和错误图。
Q 如何检测内存泄漏?
A DevTools Memory 面板查看内存趋势,持续增长不回落说明有泄漏。常见原因:Controller 未 dispose、Stream 未 cancel。
📖 小节
- DevTools 定位性能瓶颈:FPS/Rebuild/CPU/Memory 四维度分析
- const 构造函数零成本优化,能加就加
- RepaintBoundary 隔离重绘,避免小改动触发大面积重绘
- 拆分大 Widget,setState 只影响最小子树
- CachedNetworkImage 缓存图片,AutomaticKeepAlive 保持 Tab 状态
📝 作业
- 基础题(难度⭐):在 DevTools 中用 Widget Rebuild Tracker 找出频繁重建的 Widget,用 const 优化。
- 进阶题(难度⭐⭐):给商品列表添加 RepaintBoundary + itemExtent,对比优化前后 FPS。
- 挑战题(难度⭐⭐⭐):实现完整的高性能商品列表:const Widget + RepaintBoundary + CachedNetworkImage(memCacheWidth) + 分页预加载 + AutomaticKeepAliveClientMixin,profile 模式下 10000 项稳定 60fps。