Flutter 小白入门教程Flutter 小白入门教程
首页
学习指南
项目实战
Flutter 官网
编程指南
首页
学习指南
项目实战
Flutter 官网
编程指南
  • 入门基础

    • 📚 基础教程
    • 第1章 - 认识 Flutter
    • 第2章 - 环境搭建
    • 第3章 - Dart 语言基础
    • 第4章 - 第一个 Flutter 应用
    • 第5章 - Widget 基础
    • 第6章 - 布局系统
    • 第7章 - 状态管理入门
    • 第8章 - 页面导航
    • 第9章 - 资源管理
  • 进阶开发

    • 第10章 - 网络请求
    • 第11章 - 本地存储
    • 第12章 - 对话框与反馈
    • 第13章 - 列表进阶
    • 第14章 - 主题定制
    • 第15章 - 状态管理进阶
    • 第16章 - 动画入门
    • 第17章 - 常用第三方包
  • 调试与发布

    • 第18章 - 调试与性能优化
    • 第19章 - 打包与发布
  • 附录

    • 附录A - UI 框架与组件库推荐
    • 附录B - 项目结构最佳实践
    • 附录C - 国际化配置
    • 附录D - 权限处理

第18章 - 调试与性能优化

嗨,朋友!我是长安。

开发过程中,你一定会遇到各种 Bug 和性能问题。这一章,我们来学习如何使用 Flutter DevTools 调试应用,以及如何优化应用性能。

🔧 调试基础

打印日志

// 基本打印
print('Hello, World!');

// 打印对象
print('User: $user');
print('List: ${list.length} items');

// 调试模式打印(release 模式不会执行)
debugPrint('Debug info');

// 条件打印
assert(() {
  print('只在 debug 模式打印');
  return true;
}());

// 使用 log(推荐,支持更长的输出)
import 'dart:developer';
log('Long message...', name: 'MyApp');

断言调试

// 断言:条件为 false 时抛出异常(仅 debug 模式有效)
assert(user != null, 'User cannot be null');
assert(age >= 0 && age <= 150, 'Invalid age');

// 条件断点(在 IDE 中设置)
// 右键点击断点 → 设置条件

异常处理

try {
  // 可能出错的代码
  final result = int.parse(input);
} on FormatException catch (e) {
  // 处理特定异常
  print('格式错误: $e');
} catch (e, stackTrace) {
  // 处理所有异常
  print('错误: $e');
  print('堆栈: $stackTrace');
} finally {
  // 无论如何都会执行
  print('清理资源');
}

// 全局异常处理
void main() {
  // Flutter 框架异常
  FlutterError.onError = (details) {
    print('Flutter 错误: ${details.exception}');
    print('堆栈: ${details.stack}');
    // 可以上报到错误监控平台
  };

  // 异步异常
  PlatformDispatcher.instance.onError = (error, stack) {
    print('未捕获的异常: $error');
    print('堆栈: $stack');
    return true;
  };

  runApp(const MyApp());
}

🛠️ Flutter DevTools

Flutter DevTools 是官方提供的调试工具套件,功能强大。

启动 DevTools

# 方式1:命令行启动
flutter pub global activate devtools
flutter pub global run devtools

# 方式2:VS Code
# 运行应用后,点击状态栏的 "Open DevTools"

# 方式3:Android Studio
# 运行应用后,点击 "Open Flutter DevTools"

Widget Inspector(界面检查器)

用于检查 Widget 树结构和布局。

功能:

  • 查看 Widget 树结构
  • 检查 Widget 属性
  • 查看布局约束(Constraints)
  • 高亮选中的 Widget
  • 显示布局边界
// 在代码中开启布局边界显示
void main() {
  debugPaintSizeEnabled = true;  // 显示尺寸
  debugPaintBaselinesEnabled = true;  // 显示基线
  debugPaintLayerBordersEnabled = true;  // 显示图层边界
  runApp(const MyApp());
}

Performance(性能面板)

用于分析应用性能和帧率。

关键指标:

  • UI 线程:处理 Dart 代码,构建 Widget 树
  • Raster 线程:将 Widget 渲染为像素
  • 帧率:60fps 为流畅,低于 30fps 会卡顿

常见性能问题:

  1. UI 线程耗时 > 16ms → 优化 build 方法
  2. Raster 线程耗时长 → 减少图层、避免 saveLayer

Memory(内存面板)

用于分析内存使用和检测内存泄漏。

功能:

  • 查看内存使用趋势
  • 分析对象分配
  • 检测内存泄漏
  • 手动触发 GC

Network(网络面板)

用于监控网络请求。

功能:

  • 查看所有 HTTP 请求
  • 查看请求/响应详情
  • 分析请求耗时

🐛 常见错误排查

1. RenderFlex overflowed(布局溢出)

错误信息:

A RenderFlex overflowed by 42 pixels on the right.

原因: 子 Widget 超出父容器范围

解决方案:

// 错误写法
Row(
  children: [
    Text('很长很长很长很长很长的文本...'),
    Icon(Icons.star),
  ],
)

// 正确写法1:使用 Expanded
Row(
  children: [
    Expanded(
      child: Text(
        '很长很长很长很长很长的文本...',
        overflow: TextOverflow.ellipsis,  // 超出显示省略号
      ),
    ),
    Icon(Icons.star),
  ],
)

// 正确写法2:使用 Flexible
Row(
  children: [
    Flexible(
      child: Text(
        '很长很长的文本...',
        overflow: TextOverflow.ellipsis,
      ),
    ),
    Icon(Icons.star),
  ],
)

// 正确写法3:使用 SingleChildScrollView
SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: [
      Text('很长很长的文本...'),
      Icon(Icons.star),
    ],
  ),
)

2. setState() called after dispose()

错误信息:

setState() called after dispose(): _MyWidgetState#12345

原因: Widget 已销毁后还在调用 setState

解决方案:

class _MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    // 模拟异步操作
    _loadData();
  }

  Future<void> _loadData() async {
    final data = await fetchData();
    // 错误:没有检查是否已销毁
    // setState(() {
    //   _data = data;
    // });

    // 正确:检查 mounted
    if (mounted) {
      setState(() {
        _data = data;
      });
    }
  }

  @override
  void dispose() {
    // 取消订阅、计时器等
    _subscription?.cancel();
    _timer?.cancel();
    super.dispose();
  }
}

3. Null check operator used on a null value

错误信息:

Null check operator used on a null value

原因: 对 null 值使用了 ! 操作符

解决方案:

// 错误写法
final name = user!.name;  // 如果 user 是 null 就会报错

// 正确写法1:使用空安全操作符
final name = user?.name ?? '未知';

// 正确写法2:先判断
if (user != null) {
  final name = user.name;
}

// 正确写法3:使用 late(确保使用前已赋值)
late User user;

@override
void initState() {
  super.initState();
  user = User(name: '长安');  // 必须在使用前赋值
}

4. Looking up a deactivated widget's ancestor

错误信息:

Looking up a deactivated widget's ancestor is unsafe.

原因: 在 Widget 销毁后还在使用 context

解决方案:

// 错误写法
Future<void> _submit() async {
  await saveData();
  // Widget 可能已经销毁
  Navigator.pop(context);  // 报错
}

// 正确写法
Future<void> _submit() async {
  await saveData();
  if (mounted) {
    Navigator.pop(context);
  }
}

// 或者先保存 Navigator
Future<void> _submit() async {
  final navigator = Navigator.of(context);
  await saveData();
  navigator.pop();
}

5. The method 'xxx' was called on null

错误信息:

The method 'xxx' was called on null.

原因: 调用方法的对象是 null

解决方案:

// 使用空安全调用
user?.login();

// 提供默认值
(user ?? defaultUser).login();

// 先判断
if (user != null) {
  user.login();
}

⚡ 性能优化技巧

1. 使用 const 构造函数

// 不好:每次 build 都会创建新对象
Container(
  padding: EdgeInsets.all(16),
  child: Text('Hello'),
)

// 好:const 对象只创建一次
Container(
  padding: const EdgeInsets.all(16),
  child: const Text('Hello'),
)

// 自定义 Widget 也要支持 const
class MyWidget extends StatelessWidget {
  const MyWidget({super.key});  // 加 const

  @override
  Widget build(BuildContext context) {
    return const Text('Hello');  // 返回值也用 const
  }
}

2. 使用 ListView.builder

// 不好:一次性创建所有子 Widget
ListView(
  children: items.map((item) => ListTile(title: Text(item))).toList(),
)

// 好:按需创建,只创建可见的 Widget
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return ListTile(title: Text(items[index]));
  },
)

// 更好:使用 itemExtent 提升滚动性能
ListView.builder(
  itemCount: items.length,
  itemExtent: 60,  // 固定高度
  itemBuilder: (context, index) {
    return ListTile(title: Text(items[index]));
  },
)

3. 减少不必要的 rebuild

// 不好:整个页面重建
class MyPage extends StatefulWidget {
  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Text('Count: $_count'),  // 只有这个需要更新
          ExpensiveWidget(),       // 但这个也会重建
          AnotherWidget(),         // 这个也会重建
        ],
      ),
    );
  }
}

// 好:只重建需要更新的部分
class _MyPageState extends State<MyPage> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          // 使用 ValueListenableBuilder 局部更新
          ValueListenableBuilder<int>(
            valueListenable: _countNotifier,
            builder: (context, count, child) {
              return Text('Count: $count');
            },
          ),
          const ExpensiveWidget(),  // 不会重建
          const AnotherWidget(),    // 不会重建
        ],
      ),
    );
  }
}

// 或者把需要更新的部分提取成独立 Widget
class CounterText extends StatefulWidget {
  const CounterText({super.key});

  @override
  State<CounterText> createState() => _CounterTextState();
}

class _CounterTextState extends State<CounterText> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _count++),
      child: Text('Count: $_count'),
    );
  }
}

4. 图片优化

// 指定图片尺寸,避免加载过大的图片
Image.network(
  imageUrl,
  width: 200,
  height: 200,
  cacheWidth: 400,   // 缓存宽度(2x 以适应高分屏)
  cacheHeight: 400,
)

// 使用缓存
CachedNetworkImage(
  imageUrl: imageUrl,
  placeholder: (context, url) => const CircularProgressIndicator(),
  errorWidget: (context, url, error) => const Icon(Icons.error),
  memCacheWidth: 400,
  memCacheHeight: 400,
)

// 预加载图片
void initState() {
  super.initState();
  // 预加载
  precacheImage(NetworkImage(imageUrl), context);
}

5. 避免在 build 中做耗时操作

// 不好:每次 build 都会执行
@override
Widget build(BuildContext context) {
  final formattedDate = DateFormat('yyyy-MM-dd').format(date);  // 每次都创建
  final filteredList = list.where((item) => item.isActive).toList();  // 每次都过滤
  
  return ...;
}

// 好:缓存计算结果
class _MyWidgetState extends State<MyWidget> {
  late String _formattedDate;
  late List<Item> _filteredList;

  @override
  void initState() {
    super.initState();
    _updateCache();
  }

  void _updateCache() {
    _formattedDate = DateFormat('yyyy-MM-dd').format(date);
    _filteredList = list.where((item) => item.isActive).toList();
  }

  @override
  Widget build(BuildContext context) {
    return ...;  // 使用缓存的值
  }
}

6. 使用 RepaintBoundary

将频繁重绘的部分隔离,避免影响其他区域。

// 动画部分单独隔离
RepaintBoundary(
  child: AnimatedWidget(...),  // 这部分的重绘不会影响外部
)

// 复杂列表项
ListView.builder(
  itemBuilder: (context, index) {
    return RepaintBoundary(
      child: ComplexListItem(item: items[index]),
    );
  },
)

7. 懒加载

// 图片懒加载
class LazyImage extends StatefulWidget {
  final String url;
  
  const LazyImage({super.key, required this.url});

  @override
  State<LazyImage> createState() => _LazyImageState();
}

class _LazyImageState extends State<LazyImage> {
  bool _isVisible = false;

  @override
  Widget build(BuildContext context) {
    return VisibilityDetector(
      key: Key(widget.url),
      onVisibilityChanged: (info) {
        if (info.visibleFraction > 0 && !_isVisible) {
          setState(() => _isVisible = true);
        }
      },
      child: _isVisible
          ? Image.network(widget.url)
          : const SizedBox(height: 200),  // 占位
    );
  }
}

// 数据懒加载
class LazyDataPage extends StatefulWidget {
  @override
  State<LazyDataPage> createState() => _LazyDataPageState();
}

class _LazyDataPageState extends State<LazyDataPage> {
  final List<Item> _items = [];
  bool _isLoading = false;
  bool _hasMore = true;
  final _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    _loadMore();
    _scrollController.addListener(_onScroll);
  }

  void _onScroll() {
    if (_scrollController.position.pixels >=
        _scrollController.position.maxScrollExtent - 200) {
      _loadMore();
    }
  }

  Future<void> _loadMore() async {
    if (_isLoading || !_hasMore) return;
    
    setState(() => _isLoading = true);
    
    final newItems = await fetchItems(page: _items.length ~/ 20 + 1);
    
    setState(() {
      _items.addAll(newItems);
      _isLoading = false;
      _hasMore = newItems.isNotEmpty;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      itemCount: _items.length + (_hasMore ? 1 : 0),
      itemBuilder: (context, index) {
        if (index >= _items.length) {
          return const Center(child: CircularProgressIndicator());
        }
        return ListTile(title: Text(_items[index].title));
      },
    );
  }
}

📊 性能检测工具

Profile 模式运行

# Profile 模式:接近 release 性能,但可以调试
flutter run --profile

性能覆盖层

MaterialApp(
  showPerformanceOverlay: true,  // 显示性能覆盖层
  // ...
)

Timeline 分析

import 'dart:developer';

// 标记时间线
Timeline.startSync('MyOperation');
// 执行操作...
Timeline.finishSync();

// 或使用 timeSync
Timeline.timeSync('MyOperation', () {
  // 执行操作...
});

📝 小结

这一章我们学习了调试和性能优化:

调试技巧

  • 使用 print / debugPrint / log 打印日志
  • 使用 Flutter DevTools 分析应用
  • 掌握常见错误的解决方案

性能优化

  • 使用 const 构造函数
  • 使用 ListView.builder 按需加载
  • 减少不必要的 rebuild
  • 图片优化和懒加载
  • 使用 RepaintBoundary 隔离重绘

性能优化原则

  1. 先测量,后优化 - 用 DevTools 找到瓶颈
  2. 不要过早优化 - 先保证功能正确
  3. 关注用户体验 - 60fps 是目标
  4. 渐进式优化 - 每次优化一个点

💪 练习题

  1. 使用 DevTools 分析一个列表页面的性能
  2. 优化一个有 1000 条数据的列表
  3. 找出并修复一个内存泄漏问题

🚀 下一步

学会了调试和优化后,下一章我们来学习 打包与发布,把应用发布到应用商店!


由 编程指南 提供

最近更新: 2026/2/3 16:24
Contributors: 王长安
Next
第19章 - 打包与发布