← Frontend / Flutter

14_调试与DevTools

Flutter 调试技巧和 DevTools 使用

Flutter DevTools、调试技巧、常见性能问题排查。


Flutter DevTools

# 运行时自动可用,VS Code 调试时会弹出链接
# 或手动启动
dart devtools

功能面板对比 Chrome DevTools:

Flutter DevTools 面板 功能 对比 Chrome DevTools
Widget Inspector 查看 Widget 树、检查布局盒子 Elements 面板
Performance 帧率图、UI 线程 / GPU 线程耗时 Performance 面板
CPU Profiler 函数调用链、耗时分析 Profiler
Memory 内存快照、泄漏对象检测 Memory 面板
Network 网络请求监控(需 dio 配合) Network 面板
Logging 日志查看 Console

调试常用技巧

// 打印(推荐 debugPrint,自动截断长日志)
debugPrint('调试信息: $value');
print('快速调试(开发时)');

// 断言(仅 Debug 模式生效,不影响生产性能)
assert(count >= 0, 'count 不能为负数');
assert(user != null, '用户对象不应为 null');

// 检查运行模式
import 'package:flutter/foundation.dart';
if (kDebugMode)   { print('debug mode'); }
if (kReleaseMode) { /* 生产逻辑 */ }
if (kProfileMode) { /* 性能分析模式 */ }

// 性能覆盖层(显示帧率和线程耗时条)
MaterialApp(
  showPerformanceOverlay: kDebugMode,    // 开发时显示
  debugShowCheckedModeBanner: false,     // 隐藏右上角 DEBUG 标志
)

Widget Inspector 使用

  1. 打开 DevTools → Widget Inspector
  2. 点击 Select Widget 按钮
  3. 在模拟器/设备上点击任意 UI 元素
  4. 右侧面板显示:Widget 类型、属性值、父子关系

常用场景:

  • 查看某个组件的实际 padding/margin
  • 找出某个元素的 Widget 类型(方便查文档)
  • 查看 Widget 树层级(层级过深可能影响性能)

Performance 面板使用

读懂火焰图:

  • UI Thread(上半部分):Dart 代码执行,包含 build / layout / paint
  • Raster Thread(下半部分):GPU 合成,把 Canvas 指令渲染到屏幕
  • 红色卡顿帧:超过 16ms(60fps)或 8ms(120fps)的帧,需要优化

判断卡顿来源:

  • UI Thread 过长 → Dart 代码逻辑太慢,减少 build 中的计算
  • Raster Thread 过长 → 合成层太多、图片太大、复杂 shader

常见性能问题排查

// ❌ 问题一:在 build() 中做耗时计算(每次重建都计算)
Widget build(BuildContext context) {
  final sorted = List.from(bigList)..sort(); // ❌ 每次都排序!
  return ListView.builder(...);
}

// ✅ 解决:在 initState 或状态变化时计算一次
@override
void initState() {
  super.initState();
  _sortedList = List.from(bigList)..sort(); // ✅ 只算一次
}

// ❌ 问题二:整个大组件因局部数据变化而全部重建
Widget build(BuildContext context) {
  final cart = context.watch<CartModel>(); // 监听整个 CartModel
  return Scaffold(
    appBar: AppBar(title: const Text('商店')), // 不用 cart,也被重建了
    body: CartList(cart: cart),
  );
}

// ✅ 解决:用 Consumer 或 context.select 缩小重建范围
Widget build(BuildContext context) {
  return Scaffold(
    appBar: const AppBar(title: Text('商店')), // const,永不重建
    body: Consumer<CartModel>(
      builder: (ctx, cart, _) => CartList(cart: cart),
    ),
  );
}

// ❌ 问题三:大列表不用 builder
ListView(
  children: bigList.map((e) => ItemWidget(e)).toList(), // 一次性渲染全部!
)

// ✅ 解决:ListView.builder 按需渲染
ListView.builder(itemCount: bigList.length, itemBuilder: ...)

// ❌ 问题四:AnimatedBuilder 没有用 child 参数(静态部分重复重建)
AnimatedBuilder(
  animation: _controller,
  builder: (context, _) {
    return Transform.rotate(
      angle: _controller.value * 6.28,
      child: const Icon(Icons.refresh, size: 48), // ❌ 每帧都重建 Icon
    );
  },
)

// ✅ 解决:静态部分通过 child 传入(只创建一次)
AnimatedBuilder(
  animation: _controller,
  builder: (context, child) {
    return Transform.rotate(
      angle: _controller.value * 6.28,
      child: child,               // ✅ 复用,不重建
    );
  },
  child: const Icon(Icons.refresh, size: 48),
)

内存泄漏排查

// 最常见的泄漏来源:Controller / Subscription 没有 dispose

class _MyState extends State<MyWidget> {
  final _textController = TextEditingController();
  final _animController = AnimationController(vsync: this, ...);
  final _scrollController = ScrollController();
  StreamSubscription? _subscription;
  Timer? _timer;

  // ✅ 全部在 dispose 中释放
  @override
  void dispose() {
    _textController.dispose();
    _animController.dispose();
    _scrollController.dispose();
    _subscription?.cancel();
    _timer?.cancel();
    super.dispose();
  }
}

使用 Memory 面板检测泄漏:

  1. DevTools → Memory
  2. 执行可能泄漏的操作(如反复打开关闭某页面)
  3. 点击 GC(强制垃圾回收)
  4. 查看对象数量是否持续增加

常用 Flutter 命令

# 分析代码问题(类似 ESLint)
flutter analyze

# 格式化代码(类似 Prettier)
dart format lib/

# 查看当前依赖树
flutter pub deps

# 清理构建缓存(遇到奇怪错误时用)
flutter clean && flutter pub get

# 查看 app 大小分析
flutter build apk --analyze-size