2.7 调试Flutter应用
📚 核心知识点
- 日志输出(print, debugPrint)
- 断点调试
- assert 断言
- 可视化调试开关
- 性能调试工具
- DevTools 使用
- 调试技巧和最佳实践
💡 日志与断点
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
-
设置断点
- 点击行号左侧添加断点(红点)
- 或按
F9
-
启动调试
- 按
F5或点击 "Run and Debug" - 选择 "Dart & Flutter"
- 按
-
调试操作
F10- 单步跳过(Step Over)F11- 单步进入(Step Into)Shift+F11- 单步跳出(Step Out)F5- 继续运行(Continue)
Android Studio / IntelliJ
-
设置断点
- 点击行号左侧
Cmd+F8(Mac) 或Ctrl+F8(Windows)
-
启动调试
- 点击工具栏的虫子图标 🐛
- 或按
Ctrl+D
-
调试面板
- Variables - 查看变量值
- Watches - 添加监视表达式
- Call Stack - 查看调用栈
条件断点
dart
void processData(List<int> data) {
for (int i = 0; i < data.length; i++) {
// 只在 i == 50 时断点
if (i == 50) {
print('到达目标索引'); // 在此行设置断点
}
}
}
设置条件断点:
- 右键点击断点
- 选择 "Edit Breakpoint"
- 输入条件:
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();
}
查看结果:
- 运行
flutter run --profile - 打开 DevTools
- 查看 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 模式无法断点调试,但可以:
- 添加日志 (使用
print,不是debugPrint) - 错误上报(Sentry, Firebase)
- 用户反馈
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:
- 开启性能叠加层
dart
MaterialApp(showPerformanceOverlay: true)
-
观察指标:
- 绿色条:< 16ms(60fps)✅
- 红色条:> 16ms(掉帧)❌
-
使用 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(...),
)