第2章:第一个Flutter应用 —— 2.7 调试Flutter应用

2.7 调试Flutter应用

📚 核心知识点

  1. 日志输出(print, debugPrint)
  2. 断点调试
  3. assert 断言
  4. 可视化调试开关
  5. 性能调试工具
  6. DevTools 使用
  7. 调试技巧和最佳实践

💡 日志与断点

1. print() 和 debugPrint()

print() - 基本日志输出

dart 复制代码
void main() {
  print('应用启动');  // 输出到控制台
  runApp(MyApp());
}

特点:

  • ✅ 简单直接
  • ❌ 输出量大时可能被截断

debugPrint() - 限流日志(推荐)

dart 复制代码
void someFunction() {
  debugPrint('这是一条调试信息');
}

优点:

  • ✅ 自动限流,避免输出被截断
  • ✅ 只在 Debug 模式生效
  • ✅ 可以自定义输出限制

自定义 debugPrint:

dart 复制代码
void customDebugPrint(String? message, {int? wrapWidth}) {
  debugPrintSynchronously('[APP] $message');
}

void main() {
  debugPrint = customDebugPrint;  // 替换默认实现
  runApp(MyApp());
}

2. 日志级别对比

方法 说明 使用场景
print() 基本输出 简单调试
debugPrint() 限流输出 大量日志输出
log() 结构化日志 复杂调试
developer.log() 开发者日志 DevTools 集成

3. 结构化日志

dart 复制代码
import 'dart:developer' as developer;

void logWithDetails() {
  developer.log(
    '用户登录',
    name: 'app.auth',
    level: 1000,  // 日志级别
    error: null,
    time: DateTime.now(),
    sequenceNumber: 42,
  );
}

🐛 断点调试

IDE 断点调试

VS Code

  1. 设置断点

    • 点击行号左侧添加断点(红点)
    • 或按 F9
  2. 启动调试

    • F5 或点击 "Run and Debug"
    • 选择 "Dart & Flutter"
  3. 调试操作

    • F10 - 单步跳过(Step Over)
    • F11 - 单步进入(Step Into)
    • Shift+F11 - 单步跳出(Step Out)
    • F5 - 继续运行(Continue)

Android Studio / IntelliJ

  1. 设置断点

    • 点击行号左侧
    • Cmd+F8 (Mac) 或 Ctrl+F8 (Windows)
  2. 启动调试

    • 点击工具栏的虫子图标 🐛
    • 或按 Ctrl+D
  3. 调试面板

    • Variables - 查看变量值
    • Watches - 添加监视表达式
    • Call Stack - 查看调用栈

条件断点

dart 复制代码
void processData(List<int> data) {
  for (int i = 0; i < data.length; i++) {
    // 只在 i == 50 时断点
    if (i == 50) {
      print('到达目标索引');  // 在此行设置断点
    }
  }
}

设置条件断点:

  1. 右键点击断点
  2. 选择 "Edit Breakpoint"
  3. 输入条件:i == 50

✅ assert 断言

什么是断言?

断言(Assert) 只在 Debug 模式 生效,用于开发时验证条件。

dart 复制代码
void updateAge(int age) {
  assert(age >= 0, '年龄不能为负数');  // Debug 模式检查
  // 业务逻辑
}

断言的特点

flowchart LR A["assert(condition)"] B{"当前模式"} C["Debug 模式"] D["Release 模式"] E{"条件是否为真?"} F["✅ 继续执行"] G["❌ 抛出异常"] H["⚡ 忽略(不执行)"] A --> B B --> C B --> D C --> E E -->|"true"| F E -->|"false"| G D --> H style F fill:#C8E6C9 style G fill:#FFCDD2 style H fill:#FFF9C4

断言示例

dart 复制代码
class User {
  final String name;
  final int age;
  
  User({required this.name, required this.age}) {
    // 断言:名字不能为空
    assert(name.isNotEmpty, '名字不能为空');
    
    // 断言:年龄在合理范围
    assert(age >= 0 && age <= 150, '年龄必须在 0-150 之间');
  }
}

// Debug 模式:
User user = User(name: '', age: -1);  // ❌ 抛出异常

// Release 模式:
User user = User(name: '', age: -1);  // ✅ 正常创建(不安全!)

断言最佳实践

推荐使用:

dart 复制代码
// 验证参数
void divide(int a, int b) {
  assert(b != 0, '除数不能为0');
  return a / b;
}

// 验证状态
class Counter {
  int _count = 0;
  
  void increment() {
    _count++;
    assert(_count >= 0, '计数器不应该为负数');
  }
}

不推荐:

dart 复制代码
// ❌ 不要在断言中修改状态
assert(list.remove(item), '删除失败');  // Release 模式不会执行!

// ✅ 应该这样
bool success = list.remove(item);
assert(success, '删除失败');

🎨 可视化调试

1. debugPaintSizeEnabled - 显示布局边界

dart 复制代码
import 'package:flutter/rendering.dart';

void main() {
  debugPaintSizeEnabled = true;  // 开启
  runApp(MyApp());
}

效果:

  • 所有盒子显示深青色边框
  • Padding 显示浅蓝色
  • 子 Widget 周围显示深蓝色
  • 对齐显示黄色箭头
  • 空白显示灰色

2. debugPaintBaselinesEnabled - 显示文本基线

dart 复制代码
void main() {
  debugPaintBaselinesEnabled = true;
  runApp(MyApp());
}

效果:

  • 文字基线显示绿色
  • 表意基线显示橙色

3. debugPaintPointersEnabled - 显示点击区域

dart 复制代码
void main() {
  debugPaintPointersEnabled = true;
  runApp(MyApp());
}

效果:

  • 点击的对象显示深青色高亮

4. debugPaintLayerBordersEnabled - 显示图层边界

dart 复制代码
void main() {
  debugPaintLayerBordersEnabled = true;
  runApp(MyApp());
}

效果:

  • 每个图层显示橙色边框

5. debugRepaintRainbowEnabled - 重绘彩虹

dart 复制代码
void main() {
  debugRepaintRainbowEnabled = true;
  runApp(MyApp());
}

效果:

  • 重绘时显示旋转色彩
  • 用于识别不必要的重绘

⚡ 性能调试

1. 性能叠加层(Performance Overlay)

dart 复制代码
MaterialApp(
  showPerformanceOverlay: true,  // 显示性能图表
  home: HomePage(),
)

显示内容:

  • GPU 线程时间(上方)
  • UI 线程时间(下方)
  • 绿色条:性能良好(<16ms)
  • 红色条:掉帧(>16ms)

2. 调试帧时间

dart 复制代码
import 'package:flutter/foundation.dart';

void main() {
  debugPrintBeginFrameBanner = true;   // 打印帧开始
  debugPrintEndFrameBanner = true;     // 打印帧结束
  runApp(MyApp());
}

输出示例:

arduino 复制代码
▄▄▄▄▄▄▄▄ Frame 12   30s 437.086ms ▄▄▄▄▄▄▄▄
Debug print: Am I performing this work more than once per frame?
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀

3. 调试布局问题

dart 复制代码
void main() {
  debugPrintMarkNeedsLayoutStacks = true;  // 打印重新布局的堆栈
  debugPrintMarkNeedsPaintStacks = true;   // 打印重新绘制的堆栈
  runApp(MyApp());
}

4. 慢速动画

dart 复制代码
import 'package:flutter/scheduler.dart';

void main() {
  timeDilation = 5.0;  // 动画速度变为原来的 1/5
  runApp(MyApp());
}

5. Timeline 性能追踪

dart 复制代码
import 'dart:developer';

void expensiveOperation() {
  Timeline.startSync('expensive_operation');
  
  // 执行耗时操作
  for (int i = 0; i < 1000000; i++) {
    // ...
  }
  
  Timeline.finishSync();
}

查看结果:

  1. 运行 flutter run --profile
  2. 打开 DevTools
  3. 查看 Timeline 标签

6. 统计应用启动时间

bash 复制代码
flutter run --trace-startup --profile

输出文件: build/start_up_info.json

json 复制代码
{
  "engineEnterTimestampMicros": 96025565262,
  "timeToFirstFrameMicros": 2171978,
  "timeToFrameworkInitMicros": 514585,
  "timeAfterFrameworkInitMicros": 1657393
}

🛠️ DevTools

什么是 DevTools?

Flutter DevTools 是官方提供的可视化调试工具,集成了多种调试功能。

启动 DevTools

方法1:命令行

bash 复制代码
flutter pub global activate devtools
flutter pub global run devtools

方法2:IDE集成

  • VS Code: 调试时自动显示 "Open DevTools"
  • Android Studio: Tools → Flutter → Open DevTools

DevTools 功能模块

1. Inspector(检查器)

功能:

  • 📱 查看 Widget 树
  • 🎨 实时修改属性
  • 📐 查看布局信息
  • 🔍 定位 Widget

使用技巧:

dart 复制代码
// 在代码中快速定位
debugDumpApp();  // 打印 Widget 树
debugDumpRenderTree();  // 打印渲染树
debugDumpLayerTree();  // 打印图层树

2. Timeline(时间线)

功能:

  • ⏱️ 记录应用性能
  • 📊 分析帧渲染时间
  • 🎯 识别性能瓶颈

关键指标:

  • UI Thread: 应该 < 16ms
  • Raster Thread: 应该 < 16ms
  • Jank: 掉帧次数

3. Memory(内存)

功能:

  • 💾 查看内存使用
  • 📈 分析内存增长
  • 🔍 查找内存泄漏

内存快照:

dart 复制代码
// 触发垃圾回收
import 'dart:developer';

void forceGC() {
  // 在 DevTools Memory 标签中点击 GC 按钮
}

4. Performance(性能)

功能:

  • 🚀 CPU 分析
  • 📊 帧率监控
  • ⚡ 识别性能问题

5. Network(网络)

功能:

  • 🌐 查看网络请求
  • 📡 分析请求/响应
  • 🐛 调试 API 调用

6. Logging(日志)

功能:

  • 📝 查看所有日志
  • 🔍 过滤和搜索
  • 📋 导出日志

📝 调试技巧

1. 快速定位问题

dart 复制代码
// 打印当前位置
print('当前位置: ${StackTrace.current}');

// 打印变量类型
print('变量类型: ${myVar.runtimeType}');

// 打印对象详情
print('对象: ${myObject.toString()}');

2. 条件日志

dart 复制代码
void debugLog(String message) {
  if (kDebugMode) {
    print('🐛 $message');
  }
}

3. 性能监控

dart 复制代码
class PerformanceMonitor {
  static final Stopwatch _stopwatch = Stopwatch();
  
  static void start(String label) {
    _stopwatch.start();
    debugPrint('⏱️ 开始: $label');
  }
  
  static void end(String label) {
    _stopwatch.stop();
    debugPrint('✅ 完成: $label - ${_stopwatch.elapsedMilliseconds}ms');
    _stopwatch.reset();
  }
}

// 使用
PerformanceMonitor.start('加载数据');
await loadData();
PerformanceMonitor.end('加载数据');

4. Widget 重建监控

dart 复制代码
class RebuildMonitor extends StatefulWidget {
  final Widget child;
  final String name;
  
  const RebuildMonitor({
    required this.child,
    required this.name,
  });
  
  @override
  State<RebuildMonitor> createState() => _RebuildMonitorState();
}

class _RebuildMonitorState extends State<RebuildMonitor> {
  int _buildCount = 0;
  
  @override
  Widget build(BuildContext context) {
    _buildCount++;
    debugPrint('🔄 ${widget.name} 重建次数: $_buildCount');
    return widget.child;
  }
}

🎓 调试模式对比

特性 Debug Profile Release
热重载
断言
调试信息 部分
性能 最快
包大小
用途 开发调试 性能测试 生产发布

运行命令

bash 复制代码
# Debug 模式(默认)
flutter run

# Profile 模式(性能分析)
flutter run --profile

# Release 模式(发布)
flutter run --release

📝 常见问题

Q1: print 输出被截断怎么办?

A: 使用 debugPrint() 代替

dart 复制代码
// ❌ 可能被截断
print(veryLongString);

// ✅ 自动分段输出
debugPrint(veryLongString);

Q2: 如何调试 Release 模式的问题?

A: Release 模式无法断点调试,但可以:

  1. 添加日志 (使用 print,不是 debugPrint
  2. 错误上报(Sentry, Firebase)
  3. 用户反馈

Q3: DevTools 连接不上怎么办?

A:

bash 复制代码
# 1. 确认 Flutter 版本
flutter doctor

# 2. 重新安装 DevTools
flutter pub global activate devtools

# 3. 手动启动
flutter pub global run devtools

# 4. 在浏览器中打开显示的 URL

Q4: 如何判断是否有性能问题?

A:

  1. 开启性能叠加层
dart 复制代码
MaterialApp(showPerformanceOverlay: true)
  1. 观察指标:

    • 绿色条:< 16ms(60fps)✅
    • 红色条:> 16ms(掉帧)❌
  2. 使用 DevTools Timeline 分析

Q5: assert 在 Release 模式不生效怎么办?

A:

这是正常的!assert 就是设计用于 Debug 模式。

生产环境验证应该使用:

dart 复制代码
void updateAge(int age) {
  if (age < 0) {
    throw ArgumentError('年龄不能为负数');  // ✅ 总是检查
  }
}

🎓 跟着做练习

练习1:日志分级系统 ⭐⭐

目标: 实现一个简单的日志系统

dart 复制代码
enum LogLevel {
  debug,
  info,
  warning,
  error,
}

class Logger {
  static void log(String message, {LogLevel level = LogLevel.info}) {
    if (!kDebugMode && level == LogLevel.debug) {
      return;  // Release 模式不输出 debug 日志
    }
    
    final prefix = _getPrefix(level);
    final time = DateTime.now().toString().substring(11, 19);
    debugPrint('[$time] $prefix $message');
  }
  
  static String _getPrefix(LogLevel level) {
    switch (level) {
      case LogLevel.debug:
        return '🐛';
      case LogLevel.info:
        return 'ℹ️';
      case LogLevel.warning:
        return '⚠️';
      case LogLevel.error:
        return '❌';
    }
  }
}

// 使用
Logger.log('应用启动', level: LogLevel.info);
Logger.log('数据加载失败', level: LogLevel.error);

练习2:性能监控器 ⭐⭐⭐

目标: 监控 Widget 构建性能

dart 复制代码
class PerformanceWidget extends StatefulWidget {
  final Widget child;
  final String name;
  
  const PerformanceWidget({
    required this.child,
    required this.name,
  });
  
  @override
  State<PerformanceWidget> createState() => _PerformanceWidgetState();
}

class _PerformanceWidgetState extends State<PerformanceWidget> {
  final Stopwatch _stopwatch = Stopwatch();
  int _buildCount = 0;
  int _totalTime = 0;
  
  @override
  Widget build(BuildContext context) {
    _stopwatch.reset();
    _stopwatch.start();
    
    final child = widget.child;
    
    _stopwatch.stop();
    _buildCount++;
    _totalTime += _stopwatch.elapsedMicroseconds;
    
    final avgTime = _totalTime / _buildCount;
    
    debugPrint(
      '📊 ${widget.name}: '
      '构建次数=$_buildCount, '
      '本次=${_stopwatch.elapsedMicroseconds}μs, '
      '平均=${avgTime.toStringAsFixed(1)}μs'
    );
    
    return child;
  }
}

// 使用
PerformanceWidget(
  name: 'UserList',
  child: ListView.builder(...),
)

参考: 《Flutter实战·第二版》2.7节

相关推荐
奋斗的小青年!!2 小时前
Flutter浮动按钮在OpenHarmony平台的实践经验
flutter·harmonyos·鸿蒙
程序员老刘5 小时前
一杯奶茶钱,PicGo + 阿里云 OSS 搭建永久稳定的个人图床
flutter·markdown
奋斗的小青年!!8 小时前
OpenHarmony Flutter 拖拽排序组件性能优化与跨平台适配指南
flutter·harmonyos·鸿蒙
小雨下雨的雨9 小时前
Flutter 框架跨平台鸿蒙开发 —— Stack 控件之三维层叠艺术
flutter·华为·harmonyos
行者9610 小时前
OpenHarmony平台Flutter手风琴菜单组件的跨平台适配实践
flutter·harmonyos·鸿蒙
小雨下雨的雨12 小时前
Flutter 框架跨平台鸿蒙开发 —— Flex 控件之响应式弹性布局
flutter·ui·华为·harmonyos·鸿蒙系统
cn_mengbei12 小时前
Flutter for OpenHarmony 实战:CheckboxListTile 复选框列表项详解
flutter
cn_mengbei12 小时前
Flutter for OpenHarmony 实战:Switch 开关按钮详解
flutter
奋斗的小青年!!12 小时前
OpenHarmony Flutter实战:打造高性能订单确认流程步骤条
flutter·harmonyos·鸿蒙
Coder_Boy_12 小时前
Flutter基础介绍-跨平台移动应用开发框架
spring boot·flutter