第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节

相关推荐
鹏多多4 小时前
flutter图片选择库multi_image_picker_plus和image_picker的对比和使用解析
android·flutter·ios
GISer_Jing4 小时前
Flutter架构解析:从引擎层到应用层
前端·flutter·架构
lqj_本人4 小时前
Flutter与鸿蒙EventChannel事件流通信详解
flutter
lpfasd1234 小时前
Flutter持续健康发展的多维度分析
flutter
GISer_Jing4 小时前
Flutter开发全攻略:从入门到精通
android·前端·flutter
默默_david1 天前
14.5 绘制(一)绘制原理及Layer——问答
flutter
LinXunFeng2 天前
Flutter 拖拉对比组件,换装图片前后对比必备
前端·flutter·开源
2501_919749032 天前
配置flutter鸿蒙的环境和创建并运行第一个flutter鸿蒙项目【精心制作】
flutter·华为·harmonyos