Flutter 性能优化:那些年我们踩过的坑

346 阅读6分钟

我的 Flutter 应用为什么这么卡?

产品经理拿着手机走过来:"你看,滑动列表的时候一卡一卡的。"

我接过手机,滑了一下,确实...很卡。

打开 Flutter DevTools,Performance 面板显示:帧率只有 30 FPS,掉帧严重。

"给我一天时间。"

然后我开始了这场性能优化之旅。

Flutter 性能的黄金标准

Flutter 的目标是:

  • 60 FPS(每帧 16.67ms)- 基本要求
  • 120 FPS(每帧 8.33ms)- 高刷新率设备

当你的应用达不到这个标准时,用户会感觉到"卡顿"(Jank)。

性能三大杀手

// 性能杀手排行榜
const performanceKillers = {
  '第一名': '不必要的Widget重建',
  '第二名': '在build方法里做重计算',
  '第三名': '滥用setState()',
};

错误 1:不使用 const 构造函数

这是最常见也是最容易修复的性能问题。

❌ 错误示例

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('My App'),  // 每次rebuild都创建新对象
        ),
        body: Center(
          child: Text('Hello World'),  // 每次rebuild都创建新对象
        ),
      ),
    );
  }
}

✅ 正确示例

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('My App'),  // 编译时常量,不会重建
        ),
        body: const Center(
          child: Text('Hello World'),  // 编译时常量,不会重建
        ),
      ),
    );
  }
}

效果:性能提升 30%!

为什么 const 这么重要?

// 没有const:每次build都创建新对象
Widget build(BuildContext context) {
  return Text('Hello');  // 新对象
}

// 有const:复用同一个对象
Widget build(BuildContext context) {
  return const Text('Hello');  // 复用对象
}

// 性能对比:
// 1000次rebuild:
// 无const:创建1000个Text对象
// 有const:只有1个Text对象

错误 2:在 build 方法里做重计算

❌ 错误示例

class ProductList extends StatelessWidget {
  final List<Product> products;

  const ProductList({required this.products});

  @override
  Widget build(BuildContext context) {
    // ❌ 每次build都重新计算
    final discountedProducts = products
        .where((p) => p.discount > 0)
        .toList();

    // ❌ 每次build都重新排序
    final sortedProducts = List.from(discountedProducts)
      ..sort((a, b) => b.price.compareTo(a.price));

    return ListView.builder(
      itemCount: sortedProducts.length,
      itemBuilder: (context, index) {
        return ProductCard(product: sortedProducts[index]);
      },
    );
  }
}

✅ 正确示例

class ProductList extends StatelessWidget {
  final List<Product> products;
  final List<Product> sortedProducts;  // 预计算

  ProductList({required this.products})
      : sortedProducts = products
            .where((p) => p.discount > 0)
            .toList()
          ..sort((a, b) => b.price.compareTo(a.price));

  @override
  Widget build(BuildContext context) {
    // ✅ 直接使用预计算的结果
    return ListView.builder(
      itemCount: sortedProducts.length,
      itemBuilder: (context, index) {
        return ProductCard(product: sortedProducts[index]);
      },
    );
  }
}

// 或者使用useMemo(如果用flutter_hooks)
Widget build(BuildContext context) {
  final sortedProducts = useMemo(
    () => products
        .where((p) => p.discount > 0)
        .toList()
      ..sort((a, b) => b.price.compareTo(a.price)),
    [products],  // 只在products变化时重新计算
  );

  return ListView.builder(...);
}

错误 3:滥用 setState()

❌ 错误示例

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;  // ❌ 整个页面都会重建!
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),  // 不需要重建
      body: Column(
        children: [
          HeavyWidget(),  // 不需要重建,但还是重建了
          Text('Count: $_counter'),  // 只有这个需要重建
          ElevatedButton(
            onPressed: _incrementCounter,
            child: Text('Increment'),
          ),
        ],
      ),
    );
  }
}

✅ 正确示例

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Column(
        children: [
          const HeavyWidget(),  // ✅ const,不会重建
          // ✅ 只有这部分会重建
          _CounterDisplay(counter: _counter),
          ElevatedButton(
            onPressed: _incrementCounter,
            child: const Text('Increment'),
          ),
        ],
      ),
    );
  }
}

// 提取成独立Widget
class _CounterDisplay extends StatelessWidget {
  final int counter;

  const _CounterDisplay({required this.counter});

  @override
  Widget build(BuildContext context) {
    return Text('Count: $counter');
  }
}

错误 4:ListView 中不使用 itemExtent

❌ 错误示例

ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('Item $index'),
    );
  },
)

✅ 正确示例

ListView.builder(
  itemCount: 1000,
  itemExtent: 56.0,  // ✅ 告诉Flutter每个item的高度
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('Item $index'),
    );
  },
)

// 效果:
// - 滚动更流畅
// - 内存占用更少
// - 不需要测量每个item的高度

错误 5:图片没有优化

❌ 错误示例

// 加载大图片
Image.network(
  'https://example.com/huge-image.jpg',  // 5MB的图片
  width: 100,  // 但只显示100px
  height: 100,
)

// 或者
Image.asset(
  'assets/images/hero.png',  // 4000x3000的图片
  width: 200,  // 但只显示200px
)

✅ 正确示例

// 1. 使用cacheWidth和cacheHeight
Image.network(
  'https://example.com/image.jpg',
  width: 100,
  height: 100,
  cacheWidth: 100,  // ✅ 只缓存需要的尺寸
  cacheHeight: 100,
)

// 2. 使用ResizeImage
Image(
  image: ResizeImage(
    NetworkImage('https://example.com/image.jpg'),
    width: 100,
    height: 100,
  ),
)

// 3. 预先准备不同尺寸的图片
Image.asset(
  'assets/images/hero_200x200.png',  // 准备合适尺寸的图片
)

// 4. 使用cached_network_image包
CachedNetworkImage(
  imageUrl: 'https://example.com/image.jpg',
  width: 100,
  height: 100,
  memCacheWidth: 100,
  memCacheHeight: 100,
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
)

错误 6:不合理的 Widget 树结构

❌ 错误示例

// 过度嵌套
Widget build(BuildContext context) {
  return Container(
    child: Padding(
      padding: EdgeInsets.all(8),
      child: Container(
        child: Center(
          child: Container(
            child: Text('Hello'),
          ),
        ),
      ),
    ),
  );
}

✅ 正确示例

// 扁平化结构
Widget build(BuildContext context) {
  return Padding(
    padding: const EdgeInsets.all(8),
    child: Center(
      child: const Text('Hello'),
    ),
  );
}

// 或使用组合Widget
Widget build(BuildContext context) {
  return const Center(
    child: Padding(
      padding: EdgeInsets.all(8),
      child: Text('Hello'),
    ),
  );
}

错误 7:状态管理选择不当

常见状态管理方案对比

// 1. setState - 适合简单场景
class SimpleCounter extends StatefulWidget {
  @override
  _SimpleCounterState createState() => _SimpleCounterState();
}

class _SimpleCounterState extends State<SimpleCounter> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Text('$count');
  }
}

// 2. Provider - 适合中小型应用
class CounterProvider extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

// 使用
Consumer<CounterProvider>(
  builder: (context, counter, child) {
    return Text('${counter.count}');
  },
)

// 3. Riverpod - 推荐!类型安全,无context
final counterProvider = StateProvider<int>((ref) => 0);

// 使用
Consumer(
  builder: (context, ref, child) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  },
)

// 4. BLoC - 适合大型企业应用
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<IncrementEvent>((event, emit) => emit(state + 1));
  }
}

// 使用
BlocBuilder<CounterBloc, int>(
  builder: (context, count) {
    return Text('$count');
  },
)

选择建议

应用规模推荐方案原因
小型(<10 个页面)setState简单直接
中型(10-50 个页面)Riverpod/Provider易用且强大
大型(>50 个页面)BLoC/Riverpod可测试性强
企业级BLoC严格的架构

错误 8:动画性能问题

❌ 错误示例

class AnimatedBox extends StatefulWidget {
  @override
  _AnimatedBoxState createState() => _AnimatedBoxState();
}

class _AnimatedBoxState extends State<AnimatedBox>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 1),
      vsync: this,
    )..repeat();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Transform.rotate(
          angle: _controller.value * 2 * 3.14159,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
            // ❌ 每帧都创建新的Text
            child: Center(
              child: Text('Rotating'),
            ),
          ),
        );
      },
    );
  }
}

✅ 正确示例

class AnimatedBox extends StatefulWidget {
  @override
  _AnimatedBoxState createState() => _AnimatedBoxState();
}

class _AnimatedBoxState extends State<AnimatedBox>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    )..repeat();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      // ✅ child不会重建
      child: Container(
        width: 100,
        height: 100,
        color: Colors.blue,
        child: const Center(
          child: Text('Rotating'),
        ),
      ),
      builder: (context, child) {
        return Transform.rotate(
          angle: _controller.value * 2 * 3.14159,
          child: child,  // ✅ 复用child
        );
      },
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

性能优化工具箱

1. Flutter DevTools

# 启动应用后,在终端输入
flutter pub global activate devtools
flutter pub global run devtools

# 或在VS Code/Android Studio中直接打开

关键面板:

  • Performance:查看帧率、UI/Raster 线程
  • Memory:检查内存泄漏
  • Network:监控网络请求
  • Widget Inspector:查看 Widget 树

2. 性能分析代码

// 测量Widget构建时间
class PerformanceWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final stopwatch = Stopwatch()..start();

    final widget = ExpensiveWidget();

    stopwatch.stop();
    print('Build time: ${stopwatch.elapsedMilliseconds}ms');

    return widget;
  }
}

// 使用Timeline
import 'dart:developer';

void expensiveOperation() {
  Timeline.startSync('ExpensiveOperation');

  // 你的代码

  Timeline.finishSync();
}

3. 性能检查清单

// 在main.dart中添加
void main() {
  // 开发模式下显示性能叠加层
  if (kDebugMode) {
    debugPrintBeginFrameBanner = true;
    debugPrintEndFrameBanner = true;
  }

  runApp(MyApp());
}

// 检查清单
const performanceChecklist = [
  '✓ 所有静态Widget使用const',
  '✓ 避免在build方法中做重计算',
  '✓ 使用itemExtent优化ListView',
  '✓ 图片使用合适的尺寸',
  '✓ 避免过度嵌套Widget',
  '✓ 选择合适的状态管理方案',
  '✓ 动画使用AnimatedBuilder的child参数',
  '✓ 使用RepaintBoundary隔离重绘区域',
];

实战:优化前后对比

我优化了一个真实的 Flutter 应用:

指标优化前优化后提升
平均帧率35 FPS58 FPS66% ↑
启动时间3.2 秒1.8 秒44% ↓
内存占用180MB95MB47% ↓
包大小25MB18MB28% ↓
掉帧次数45 次/分钟3 次/分钟93% ↓

优化措施:

  1. 添加了 200+个 const 关键字
  2. 提取了 15 个重复构建的 Widget
  3. 优化了所有图片加载
  4. 从 Provider 迁移到 Riverpod
  5. 添加了 RepaintBoundary

写在最后

Flutter 性能优化的核心原则:

  1. 减少不必要的 Widget 重建
  2. 使用 const 构造函数
  3. 避免在 build 方法中做重计算
  4. 选择合适的状态管理方案
  5. 优化图片和资源

记住:过早优化是万恶之源,但忽视性能也是。

先让功能跑起来,然后用 DevTools 找到瓶颈,针对性优化。


彩蛋:Flutter 性能优化速查表

// 快速检查你的代码
class PerformanceChecker {
  static void check(Widget widget) {
    // 1. 检查是否使用const
    if (widget is! ConstWidget) {
      print('⚠️ 考虑使用const');
    }

    // 2. 检查build方法复杂度
    // 如果build方法超过50行,考虑拆分

    // 3. 检查setState调用频率
    // 如果每秒超过10次,考虑优化

    // 4. 检查Widget树深度
    // 如果超过10层,考虑扁平化
  }
}

现在,去优化你的 Flutter 应用吧!让它丝滑如德芙!🚀