Appearance
Flutter 排错手册
Flutter 排错先分类,再动手。不要一上来就清缓存、删 lock、换 SDK,否则容易把问题变成多个问题。
有效排错的顺序是:
- 先判断问题发生在哪个阶段:环境、依赖、构建、运行、布局、网络、性能、发布。
- 保留第一条真实错误,不要只看最后一行失败提示。
- 用最小复现缩小范围:新项目是否能跑、换设备是否复现、去掉插件是否恢复。
- 只改一个变量,然后验证。
- 记录最终原因和处理方式,避免下次重复踩坑。
排查入口
| 问题 | 优先检查 |
|---|---|
| 环境异常 | flutter doctor |
| 依赖冲突 | pubspec.yaml、pubspec.lock、SDK constraint |
| Android 构建失败 | Gradle、Kotlin、minSdk、namespace、签名 |
| iOS 构建失败 | CocoaPods、Xcode、证书、描述文件、权限 |
| 页面白屏 | 日志、异常栈、路由、状态机、首帧 |
| 布局溢出 | Row/Column 主轴空间、滚动容器约束 |
| 接口联调异常 | 请求参数、环境域名、token、错误码、代理 |
| 权限异常 | Manifest、Info.plist、运行时授权、系统设置 |
| 卡顿 | Profile mode、DevTools Performance、图片和重建范围 |
常用命令:
bash
flutter doctor -v
flutter devices
flutter analyze
flutter test
flutter run -v
flutter clean
flutter pub get
flutter pub deps
flutter pub outdatedflutter clean 是清理构建产物,不是万能药。它适合构建缓存或平台构建产物异常,但如果根因是依赖冲突、权限缺失、Gradle 版本不匹配,清理后仍然会失败。
依赖问题
bash
flutter pub get
flutter pub outdated
flutter pub depsdependency_overrides 只适合临时验证,不建议长期作为正式解法。
| 症状 | 可能原因 | 处理 |
|---|---|---|
version solving failed | 依赖版本约束冲突 | 看冲突链,升级或降低相关包 |
| 某插件只在一个平台失败 | 插件平台实现缺失或版本不兼容 | 查插件支持平台和原生配置 |
| 升级 Flutter 后大量报错 | SDK、插件、Dart 语法约束变化 | 分阶段升级,不要一次升所有依赖 |
| 本地能跑 CI 失败 | 系统环境、缓存、大小写路径差异 | 固定 SDK 版本,检查路径大小写 |
排依赖冲突时,先看直接依赖,再看传递依赖。不要只盯着报错包名,有时真正需要升级的是上游依赖。
构建问题
Android 构建失败时,优先找第一条真实错误,而不是最后一行 BUILD FAILED。常见原因包括 Gradle 插件版本、Kotlin 版本、minSdk、namespace、签名和混淆。
iOS 构建失败时,重点看 CocoaPods、Xcode 版本、最低系统版本、证书、描述文件和 Info.plist 权限文案。
Android 常见问题
| 症状 | 检查点 |
|---|---|
| Gradle 下载失败 | 网络、镜像、Gradle wrapper、代理 |
minSdkVersion 不满足 | 插件要求更高 minSdk |
namespace not specified | Android Gradle Plugin 新版本需要 namespace |
| Kotlin 编译失败 | Kotlin 插件版本和依赖不匹配 |
| release 包运行异常 | 混淆、资源压缩、签名、ABI |
| 权限相关功能无效 | AndroidManifest.xml、运行时权限、系统版本 |
Android 排查时可以使用:
bash
cd android
./gradlew assembleDebug --stacktrace
./gradlew app:dependenciesiOS 常见问题
| 症状 | 检查点 |
|---|---|
| Pod install 失败 | CocoaPods 版本、Podfile、Ruby 环境 |
| Xcode 编译失败 | Xcode 版本、最低 iOS、Swift 版本 |
| 真机安装失败 | 证书、描述文件、Bundle ID |
| 权限弹窗不出现 | Info.plist 权限文案缺失 |
| 插件链接失败 | Pod 依赖、架构、原生 SDK 版本 |
iOS 排查时不要忽略 Xcode 的完整错误。很多 Flutter 命令只展示摘要,真实原因要去 Xcode build log 里看。
白屏问题
白屏要分三类:
| 类型 | 检查点 |
|---|---|
| 启动白屏 | 初始化阻塞、首帧过慢、启动图配置 |
| 页面白屏 | 路由错误、异常被吞、布局不可见 |
| 数据白屏 | 接口失败但没有错误态、状态机缺分支 |
排查白屏可以按这个顺序:
- 看控制台是否有异常栈。
- 确认
main()、runApp()、根路由是否执行。 - 检查首屏初始化是否有同步阻塞。
- 检查页面是否进入加载态但永远不结束。
- 检查接口失败后是否缺少错误态。
- 检查布局是否不可见,例如高度为 0、颜色同背景、被遮挡。
- release 才白屏时,检查混淆、资源、权限、初始化顺序。
不要在页面里吞掉异常后只返回空容器。至少要记录日志,并在用户界面给出失败状态和重试入口。
布局溢出
典型报错包括 RenderFlex overflowed、Vertical viewport was given unbounded height、BoxConstraints forces an infinite width。布局问题通常来自约束不清。
| 场景 | 原因 | 处理 |
|---|---|---|
Row 文本溢出 | 文本没有被约束宽度 | 用 Expanded 或 Flexible |
Column 中列表报无限高度 | ListView 不知道自己高度 | 用 Expanded 包裹 |
滚动容器里套 Expanded | 滚动方向约束是无限的 | 改用固定高度或 sliver |
| 图片撑爆布局 | 图片原始尺寸大,显示约束不清 | 设置宽高、fit、裁剪 |
| 键盘弹起遮挡 | 页面没有处理 inset | 使用 Scaffold、滚动容器或调整布局 |
排查时可以打开 Flutter Inspector,选中出问题的 Widget,看父子约束。不要只靠肉眼移动几个 padding。
状态和重建问题
状态问题常见表现:
| 症状 | 可能原因 |
|---|---|
| 页面反复请求接口 | 请求放在 build 中 |
| 列表项状态错乱 | 缺少稳定 Key |
| 输入框内容丢失 | 控制器生命周期不对 |
| 页面退出后仍收到回调 | 订阅、定时器、控制器没有释放 |
| 局部操作导致整页闪烁 | 状态作用域过大 |
| 数据更新 UI 不刷新 | 状态对象没有通知或引用未变化 |
处理原则:
- 不在
build中制造副作用。 - 控制器、订阅、动画在
dispose释放。 - 列表项使用业务 ID 作为 Key。
- 高频变化拆成更小组件或更小状态作用域。
- 页面状态用明确模型表达加载、成功、空、失败。
网络和异步问题
网络问题不要只显示“请求失败”。至少要区分:
| 类型 | 例子 | 处理 |
|---|---|---|
| 连接问题 | 无网络、超时、DNS | 可重试,保留缓存 |
| 认证问题 | token 过期、401 | 刷新 token 或跳登录 |
| 权限问题 | 403 | 提示无权限 |
| 业务错误 | 参数错误、状态不可操作 | 展示业务文案 |
| 服务错误 | 500、网关错误 | 重试或稍后再试 |
| 解析错误 | 字段类型不匹配 | 记录日志,降级处理 |
异步回调还要注意页面生命周期。页面已经销毁后再调用 setState,会出现 setState() called after dispose()。这种问题常见于网络请求、定时器、流订阅和动画回调。处理方式是取消订阅、判断 mounted,或者把异步逻辑放到更合适的状态管理对象里。
接口联调问题
联调阶段要先确认请求真的发到了正确服务。常见问题包括:
| 现象 | 检查点 |
|---|---|
| 本地正常,测试包失败 | --dart-define 环境、接口域名、证书、代理 |
| 401 或频繁退出登录 | token 存储、刷新 token、时间偏差、账号互踢 |
| 403 | 权限、角色、租户、接口灰度 |
| 字段解析失败 | DTO 可空、字段类型、后端兼容变更 |
| 分页重复或丢数据 | 排序规则、游标、刷新和加载更多状态 |
| 上传失败 | 文件大小、MIME、权限、超时、后台限制 |
页面不要直接展示后端原始错误。更稳的做法是把接口错误映射成应用错误,再由页面决定提示、重试、跳转登录或保留缓存。
权限和设备能力问题
设备能力相关问题通常跨 Dart、插件和原生配置三层:
| 能力 | 检查点 |
|---|---|
| 相机/相册 | 权限文案、运行时授权、系统相册限制、文件路径 |
| 定位 | 前台/后台权限、系统定位开关、精确定位、服务不可用 |
| 通知 | 通知权限、厂商通道、token 注册、点击跳转 |
| 蓝牙 | 系统版本权限变化、扫描限制、定位权限依赖 |
| 支付/分享 | SDK 配置、回调 URL、签名、应用白名单 |
调试这类问题时,要分别验证:原生配置是否齐全、插件是否支持当前平台、用户授权状态是否符合预期、异常是否能被业务层接住。
卡顿问题
卡顿不要用 debug 模式判断。用 profile mode 和 DevTools 看 UI 线程、Raster 线程、图片解码、布局和绘制耗时。
常见处理:
- 缩小
setState影响范围。 - 使用
ListView.builder懒构建。 - 避免
shrinkWrap处理大列表。 - 图片按显示尺寸加载。
- 复杂绘制用
RepaintBoundary验证隔离效果。 - 大计算移出主 isolate。
性能问题要先分类:
| 类型 | 表现 | 排查 |
|---|---|---|
| 启动慢 | 点开应用很久才显示首屏 | 初始化、同步 IO、插件启动、资源加载 |
| 滚动卡 | 列表滑动掉帧 | 列表项过重、图片过大、重建范围大 |
| 动画卡 | 过渡不流畅 | 每帧计算、绘制、图层复杂度 |
| 内存高 | 用久后变慢或崩溃 | 图片缓存、对象泄漏、订阅未释放 |
| 某页面慢 | 进入页面明显停顿 | 同步计算、首屏接口、复杂布局 |
优化不要凭感觉。先用 DevTools 找到瓶颈属于 build、layout、paint、raster、network 还是 memory,再做有针对性的改动。每次优化后要保留对比数据。
应用发布阶段问题
有些问题 debug 没有、release 才出现:
| 问题 | 可能原因 |
|---|---|
| release 白屏 | 混淆、资源缺失、初始化异常、权限 |
| 某插件 release 失效 | ProGuard/R8 规则缺失、原生 SDK 配置差异 |
| iOS 审核被拒 | 权限文案、隐私说明、后台能力声明不完整 |
| 线上崩溃无法定位 | 没接崩溃收集或符号化信息缺失 |
| 不同渠道行为不同 | 环境变量、接口域名、签名、配置文件混用 |
发布前至少确认:
- 环境配置明确区分 dev、test、prod。
- Android 签名、混淆、渠道和版本号正确。
- iOS Bundle ID、证书、描述文件和权限文案正确。
- 崩溃收集、日志、关键埋点可用。
- 有回滚或紧急修复路径。
排错闭环模板
遇到问题时可以按下面模板记录:
text
问题现象:
发生环境:Flutter 版本、Dart 版本、设备、系统版本、debug/profile/release
复现步骤:
第一条真实错误:
已排除项:
根因:
修复方式:
验证结果:
后续预防:这类记录比“已修复”更有价值。Flutter 项目很多问题会在团队、设备、插件、系统版本变化后重复出现,沉淀排错记录能明显降低后续成本。
