Flutter: 性能优化

性能是体验的底线——60fps 是承诺,59fps 是失败。

📋 前置知识:需要先掌握以下内容

1. 你将学到


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. 性能优化全景

100%
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。

📖 小节


📝 作业

  1. 基础题(难度⭐):在 DevTools 中用 Widget Rebuild Tracker 找出频繁重建的 Widget,用 const 优化。
  2. 进阶题(难度⭐⭐):给商品列表添加 RepaintBoundary + itemExtent,对比优化前后 FPS。
  3. 挑战题(难度⭐⭐⭐):实现完整的高性能商品列表:const Widget + RepaintBoundary + CachedNetworkImage(memCacheWidth) + 分页预加载 + AutomaticKeepAliveClientMixin,profile 模式下 10000 项稳定 60fps。

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏