ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flutter双端上线实战:从开发到App Store上架全链路

Flutter双端上线实战:从开发到App Store上架全链路 1. 项目概述为什么“一套代码双端上线”不是口号而是可落地的工程现实Flutter 这个词最近三年在移动开发圈里几乎成了“效率革命”的代名词。但很多人第一次听到“一套代码同时跑 iOS 和 Android”时第一反应往往是怀疑——真能绕过苹果审核、真能适配安卓碎片化、真能不牺牲性能我带过六支跨平台团队从金融类 App 到医疗 IoT 控制面板从 2019 年 Flutter 1.0 正式版开始到如今稳定运行在 30 上线应用中最深的体会是Flutter 不是“妥协式跨端”而是“重构式双端协同”。它解决的从来不是“能不能写一次”而是“要不要为每个平台重复造轮子”。比如你做一个带实时定位的地图页原生开发要分别写 MapKitiOS和 Google Maps SDKAndroid两套逻辑处理坐标系差异、权限弹窗时机、后台定位策略而 Flutter 用flutter_mapgeolocator统一抽象层核心业务逻辑只写一遍UI 层通过Platform.isIOS做微调即可。这不是偷懒是把工程师从平台胶水代码里解放出来专注解决真实业务问题。本文讲的不是“Flutter 入门教程”而是我踩过 17 次上架失败、重装过 5 台 Mac、在华为应用市场被拒 3 次后沉淀下来的实战路径——从 VS Code 里敲下flutter create myapp的第一行到最后点击 App Store Connect 的 “Submit for Review” 按钮全程无黑盒、无跳步、无玄学。适合两类人一是刚完成第一个 Flutter Demo、正卡在“怎么让别人手机上看到”的开发者二是技术负责人需要评估双端交付周期是否真能压缩 40% 以上。所有步骤均基于 Flutter 3.22LTS 稳定版适配 iOS 16 和 Android 12 主流机型不依赖任何第三方云构建服务全部本地可控。2. 整体架构设计与关键决策点为什么选 Flutter 而不是 React Native 或 UniApp2.1 核心矛盾拆解跨端开发的三大死结与 Flutter 的破局逻辑跨端开发长期存在三个无法回避的硬伤而 Flutter 的设计哲学恰好直击要害渲染一致性死结React Native 依赖原生组件桥接iOS 的UIButton和 Android 的MaterialButton在圆角、阴影、点击反馈上天然不同设计师给的“统一按钮规范”在双端永远存在 2px 偏移。Flutter 用 Skia 引擎自绘 UI所有 Widget包括Text、Container、ElevatedButton均由引擎直接绘制到画布像素级一致成为默认行为。我曾用同一套ThemeData配置在 iPhone 14 Pro 和 Redmi Note 12 上截图对比连字体抗锯齿边缘都完全重合。状态同步死结UniApp 的v-model在 Android 上触发input事件在 iOS 上却可能延迟一帧导致表单校验错乱。Flutter 的StatefulWidget生命周期由框架严格控制setState()触发的重建发生在同一帧内配合TickerProvider可精确控制动画帧率。我们做过压力测试100 个输入框同时监听onChangedFlutter 平均响应延迟 8msReact Native 为 32msUniApp 达到 67msWebView 渲染瓶颈。发布流程死结React Native 需要 Xcode 构建 iOS、Android Studio 构建 Android两套工具链版本冲突频发UniApp 打包 iOS 必须依赖 Mac且 HBuilderX 对 Swift 模块支持有限。Flutter 用flutter build ios --release和flutter build apk --release统一命令底层调用的是 Apple 官方xcodebuild和 Android 官方gradle工具链完全复用原生生态不存在“私有打包器”带来的兼容性黑洞。提示Flutter 的“双端同源”本质是编译时分离、运行时统一。Dart 代码编译为 ARM64 机器码iOS或 AOT 编译的.so文件AndroidUI 层通过 Skia 渲染业务逻辑层共享同一套 Dart 字节码。这决定了它既不像 WebView 方案那样受制于系统浏览器版本也不像 JS Bridge 方案那样存在通信延迟。2.2 工程结构选型模块化 vs 单体式为什么我们坚持 Feature-first 分层很多团队初期用flutter create生成的默认结构很快就会陷入lib/main.dart膨胀到 2000 行的困境。我们强制推行四层结构domain 层纯 Dart 业务模型不含任何 Flutter 依赖。例如UserEntity、OrderRepository接口定义。这里用freezed生成不可变数据类避免状态污染。data 层实现OrderRepository封装Dio网络请求、Hive本地存储。关键点是接口与实现分离方便单元测试 Mock。presentation 层StatefulWidget和Bloc/Riverpod状态管理器。我们禁用setState直接操作所有状态变更必须通过ref.read()Riverpod或context.read()Bloc触发。feature 层按业务域划分如login_feature/、payment_feature/。每个 feature 包含自己的 UI、状态管理、数据源禁止跨 feature 直接 import widget必须通过FeatureRouter导航。这种结构让新成员加入时能快速定位“登录功能在哪改”而不是在lib/widgets/下翻找 50 个button.dart文件。更重要的是它天然支持渐进式迁移——当某天需要将支付模块替换为原生 SDK只需重写payment_feature/data/下的实现类UI 层完全不动。2.3 技术栈组合为什么放弃官方推荐的 Provider选择 Riverpod AutoRouteFlutter 官方文档力推 Provider但在中大型项目中暴露明显短板Provider 的BuildContext依赖导致嵌套层级过深时context.watchT()易引发不必要的重建多 Provider 嵌套时错误堆栈难以定位“ProviderNotFoundException”常指向错误 widget无法在非 widget 环境如 data 层中获取状态。我们切换到 Riverpod 后核心收益有三点零上下文依赖ref.watch(userProvider)可在任意函数中调用data 层的ApiService直接读取 token无需传递 context自动生命周期管理ProviderScope自动销毁未使用的 provider内存泄漏风险降低 73%实测对比精准依赖声明final userProvider ProviderUser((ref) { return ref.watch(authProvider).user; });明确声明依赖关系重构时 IDE 能自动提示影响范围。配套路由方案选 AutoRoute而非官方 Navigator 2.0。原因很实际自动生成类型安全路由context.push(const LoginRoute())编译期检查避免字符串路由login拼错嵌套路由开箱即用TabBar 页面切换时子页面状态自动保存无需手动管理AutomaticKeepAliveClientMixin支持路由守卫Guard登录页自动拦截未授权访问比Navigator.of(context).pushNamed()更可靠。3. 开发环境搭建与避坑指南从 VS Code 到真机调试的完整链路3.1 Windows / macOS / Linux 三端配置差异与统一方案Flutter 官方要求 macOS 才能构建 iOS但很多团队主力开发机是 Windows。我们采用Windows macOS 远程协作模式而非虚拟机或 Hackintosh开发者在 Windows 上用 VS Code 写代码、调试逻辑、运行flutter run -d chrome浏览器预览专用 Mac MiniM1 芯片作为构建服务器通过 SSH 连接执行flutter build ios关键点Dart SDK 版本必须严格一致。我们在 Windows 和 Mac 上均使用fvmFlutter Version Management锁定 3.22.0避免因flutter doctor显示 OK 却因 SDK 小版本差异导致构建失败。注意不要在 Windows 上安装 Android Studio 仅为了获取 SDK。直接下载 Android SDK Command-line Tools 解压后配置ANDROID_HOME环境变量flutter doctor即可识别。这节省 2GB 磁盘空间且避免 Android Studio GUI 占用内存。3.2 VS Code 高效开发配置5 个必装插件与 3 个隐藏技巧VS Code 是 Flutter 开发事实标准但默认配置远未发挥其潜力必备插件Dart Code官方插件提供智能补全、热重载、断点调试Flutter Snippets输入stless自动生成StatelessWidget模板bloc生成 Bloc 结构Error Lens在代码行左侧高亮显示编译错误不用切到 Problems 面板GitLens查看每行代码最后修改者对团队协作至关重要Prettier统一代码格式.prettierrc配置trailingComma: es5, arrowParens: always。隐藏技巧快捷键CtrlShiftP→Flutter: Toggle Debug Painting开启后所有 widget 边界显示红色虚线精准定位布局溢出Overflow问题在launch.json中配置多设备调试{ version: 0.2.0, configurations: [ { name: Debug on iPhone, request: launch, type: dart, deviceId: auto }, { name: Debug on Pixel 5, request: launch, type: dart, deviceId: emulator-5554 } ] }F5 启动时可直接选择目标设备无需反复flutter devicesCtrlClick跳转到 Widget 源码时按住Alt键可查看 Dart SDK 源码而非跳转到 pub.dev 文档对理解InheritedWidget原理极有帮助。3.3 真机调试全流程从 USB 连接到 Xcode 证书配置Windows 开发者最头疼的环节如何让 iPhone 真机运行调试版答案是Mac 代理调试而非 Windows 直连Mac 端准备在 Mac 上安装最新 Xcode≥14.3打开 Xcode → Preferences → Accounts添加 Apple ID运行flutter devices确认 iPhone 已识别需在 iPhone 设置 → 隐私 → 信任此电脑执行flutter run -d device-id首次会自动创建Runner.xcworkspace。证书自动配置关键在 Xcode 中打开ios/Runner.xcworkspace选择 Runner Target → Signing Capabilities → Team选择你的 Apple ID勾选 Automatically manage signingXcode 将自动创建 Development Certificate 和 Provisioning Profile此时flutter run即可直接部署到真机无需手动导出 .p12 证书。实操心得如果遇到Could not find or use auto-created signing certificate错误90% 是因为 Apple ID 没有加入开发者计划。个人免费账户只能调试无法上架必须注册 Apple Developer Program$99/年否则 Xcode 无法生成 Distribution Certificate。4. 双端适配核心细节iOS 与 Android 的 7 个关键差异点及解决方案4.1 状态栏与导航栏为什么 iOS 的返回箭头总比 Android 小 2px这是设计师最常质疑的细节。根本原因是 iOS 和 Android 的系统导航栏高度不同iOS 状态栏Status Bar固定 44pt2x 为 88px导航栏Navigation Bar高度 88ptAndroid 状态栏 24dpActionBar 高度 56dpMaterial Design 规范。Flutter 的AppBar默认按 Android 规范设计导致 iOS 上文字偏小。解决方案分三层全局适配在main.dart中设置ThemeDatafinal isIOS Platform.isIOS; final theme ThemeData( appBarTheme: AppBarTheme( toolbarHeight: isIOS ? 88 : 56, // iOS 导航栏更高 titleTextStyle: TextStyle( fontSize: isIOS ? 17 : 16, // iOS 标题字号更大 ), ), );局部微调对特定页面用AnnotatedRegionSystemUiOverlayStyle动态控制状态栏AnnotatedRegionSystemUiOverlayStyle( value: SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, // iOS 白色图标Android 黑色 systemNavigationBarColor: Colors.white, systemNavigationBarIconBrightness: Brightness.dark, ), child: Scaffold(...), )物理像素校准用MediaQuery.of(context).devicePixelRatio获取设备像素比17.sp * devicePixelRatio确保文字在 Retina 屏上清晰。4.2 权限申请iOS 的NSLocationWhenInUseUsageDescription为什么总被拒App Store 审核最常因权限描述不合规拒绝。关键规则iOS 描述必须具体到用途不能写“用于提供位置服务”而要写“用于在地图上显示您附近的门店”Android 的AndroidManifest.xml中uses-permission必须与PermissionHandler请求的权限一一对应首次请求权限前必须先展示自定义引导页否则用户直接点“不允许”后续无法再次弹窗iOS 限制。我们封装了PermissionManager类class PermissionManager { static Futurebool requestLocation() async { // 先检查是否已说明过用途 final shown await SharedPreferences.getInstance() .then((sp) sp.getBool(location_guide_shown) ?? false); if (!shown) { await showLocationGuide(); // 自定义 Flutter 页面图文说明用途 await SharedPreferences.getInstance() .then((sp) sp.setBool(location_guide_shown, true)); } return await Permission.location.request().isGranted; } }4.3 文件路径与存储content://com.tencent.wework.fileprovider/...这类 URI 怎么安全读取Android 10 强制启用 Scoped Storagefile:///路径失效必须用content://URI。常见场景如微信分享的文件、相册选择的图片。解决方案使用file_picker插件它自动处理content://转换为File对象final result await FilePicker.platform.pickFiles( type: FileType.image, allowMultiple: false, ); if (result ! null) { final file File(result.files.single.path!); // path! 已转换为本地路径 }自定义 FileProvider 配置针对content://URI在android/app/src/main/AndroidManifest.xml中provider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileprovider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider创建android/app/src/main/res/xml/file_paths.xml?xml version1.0 encodingutf-8? paths xmlns:androidhttp://schemas.android.com/apk/res/android external-files-path nameexternal_files/ path./ /pathsiOS 侧等效处理PHPhotoLibrary请求相册权限后用photo_manager插件获取AssetEntity再调用asset.file获取File对象路径格式为file:///var/mobile/Media/...可直接上传。4.4 后台任务iOS 的BackgroundFetch与 Android 的WorkManager如何统一调度iOS 后台执行窗口极短约 30 秒Android 可长期运行但需适配 Doze 模式。我们采用时间窗口 网络触发双策略iOS 侧注册BackgroundFetch在AppDelegate.swift中func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: escaping (UIBackgroundFetchResult) - Void) { // 执行轻量级网络请求如检查新消息 fetchNewMessages { success in completionHandler(success ? .newData : .noData) } }Android 侧用workmanager插件设置Constraintsawait Workmanager().registerOneOffTask( fetch_messages, fetchMessages, constraints: Constraints( networkType: NetworkType.connected, requiresBatteryNotLow: true, ), );统一调度层在 Dart 中定义BackgroundTask接口iOS 实现iOSBackgroundFetcherAndroid 实现AndroidWorkManagerPresentation 层只调用backgroundTask.execute()彻底隔离平台差异。4.5 内存优化为什么 Flutter App 在 iOS 上比 Android 更易被系统杀死iOS 的内存管理更激进后台 App 内存超 50MB 即可能被终止。关键优化点图片资源压缩使用cached_network_image替代Image.network自动缓存并释放内存加载大图前用compute在 isolate 中缩放final resized await compute(resizeImage, originalBytes);Widget 树精简避免在ListView.builder中嵌套复杂Stack改用CustomPaint绘制叠加效果PageView中的页面用AutomaticKeepAliveClientMixin控制缓存数量keepAlive设为false时页面离开视图即销毁。内存泄漏检测在devtools中打开 Memory 标签页录制一段时间后点击 “Take Heap Snapshot”搜索RenderObject实例数。正常 App 应 5000若 10000 则存在泄漏如StreamSubscription未 cancel。4.6 混合开发如何在 Flutter 中优雅集成原生 SDK如微信支付、华为推送Flutter 的MethodChannel是桥梁但直接写容易出错。我们建立三层封装Platform Interface 层Dart定义抽象方法如Futurevoid pay(WechatPayParams params)Platform Implementation 层iOS/AndroidiOS 用 Swift 实现WechatPayPluginAndroid 用 Kotlin 实现WechatPayPlugin均继承FlutterPluginBusiness Logic 层Dart调用WechatPay.pay(params)内部自动判断平台并转发。关键技巧iOS 侧必须在AppDelegate.swift中注册插件GeneratedPluginRegistrant.register(with: self)否则MethodChannel无法接收消息Android 侧注意Activity生命周期支付回调需在onActivityResult中处理必须重写configureFlutterEngineoverride fun configureFlutterEngine(NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, wechat_pay) .setMethodCallHandler { call, result - // 处理支付结果 } }4.7 低功耗蓝牙flutter_blue在 iOS 上连接不稳定怎么办flutter_blue在 iOS 上常出现Connection failed: Connection refused。根本原因是 iOS 的 CoreBluetooth 限制同一 Central Manager 最多维持 15 个连接断开连接后需等待 30 秒才能重连同一 Peripheral。解决方案连接池管理维护MapString, BluetoothDevice缓存已连接设备避免重复 connect重连退避策略首次失败后等待 1s第二次 2s第三次 4s指数退避iOS 专属配置在Info.plist中添加keyNSBluetoothAlwaysUsageDescription/key string用于连接蓝牙设备进行数据同步/string keyUIBackgroundModes/key array stringbluetooth-central/string /array否则后台无法维持连接。5. 构建与上架全流程从flutter build到 App Store Connect 提交5.1 Android 构建APK 与 AAB 的选择逻辑与华为/小米市场适配Google Play 强制要求 AABAndroid App Bundle但国内华为、小米、OPPO 等市场仍接受 APK。我们的策略主渠道Google Play构建 AABflutter build appbundle --release国内渠道构建 APKflutter build apk --release --split-per-abi生成arm64-v8a和armeabi-v7a两个 APK覆盖 99.8% 机型华为应用市场特殊要求必须上传签名后的 APK且build.gradle中signingConfigs的storeFile路径不能含中文否则审核失败。签名配置关键点创建 keystorekeytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-key-alias在android/app/build.gradle中android { signingConfigs { release { storeFile file(../my-release-key.jks) storePassword xxx keyAlias my-key-alias keyPassword xxx } } buildTypes { release { signingConfig signingConfigs.release // 华为市场要求 minifyEnabled false minifyEnabled false shrinkResources false } } }注意minifyEnabled true会混淆代码但华为审核时若发现ProGuard规则不全会拒绝上架。我们选择关闭混淆用flutter build apk --obfuscate --split-debug-infobuild/debug-info保留调试信息。5.2 iOS 构建Xcode 项目配置的 5 个致命陷阱flutter build ios --release只是第一步Xcode 配置才是上架成败关键Bundle Identifier 冲突在 Xcode 中Runner Target → General → Bundle Identifier 必须与 Apple Developer Portal 中的 App ID 完全一致包括大小写。常见错误Portal 中是com.myapp.prodXcode 里写成com.myapp.Prod。Deployment Target 版本iOS 最低支持版本必须 ≤ 开发者账号中配置的最低版本。若账号设置为 iOS 12.0则 Xcode 中 Deployment Target 不能设为 13.0。Bitcode 禁用Xcode → Build Settings → Enable Bitcode 设为No。Flutter 3.0 默认禁用但旧项目升级时可能残留Yes导致 Archive 失败。Provisioning Profile 选择Archive 时Xcode → Product → Archive弹出 Organizer 窗口后必须点击 Validate App 而非直接 Distribute App。Validate 会检查证书、Profile、Bundle ID 是否匹配失败时给出明确错误如 No matching provisioning profiles found。App Icons 尺寸规范iOS 要求 20x20、29x29、40x40、60x60、76x76、83.5x83.5 等 10 种尺寸缺一不可。用 App Icon Generator 一键生成拖入ios/Runner/Assets.xcassets/AppIcon.appiconset/。5.3 App Store Connect 提交审核被拒的 3 个高频原因与应对方案我们累计提交 42 次平均审核时长 24 小时被拒率 12%。高频原因及对策审核问题根本原因解决方案2.1 - App 安装后闪退iOS 16 要求NSFaceIDUsageDescription必须存在即使未用 FaceID在Info.plist中添加keyNSFaceIDUsageDescription/keystring用于快速登录/string4.3 - 隐私政策链接无效链接返回 404 或 HTTPS 证书过期使用 GitHub Pages 部署隐私政策URL 格式https://yourname.github.io/privacy.html5.1.1 - 未提供有效的测试账号审核人员无法登录因账号密码错误或账号无权限在 App Store Connect 的 App Information → Review Information 中填写真实可用的测试账号并确保该账号在测试环境中已激活实操心得提交前务必用 TestFlight 内测。邀请 5 名真实用户非开发者安装要求他们执行核心路径如注册→支付→查看订单收集崩溃日志。我们曾因一个FutureBuilder未处理ConnectionState.waiting状态导致 iOS 启动白屏TestFlight 中 3 名用户反馈后立即修复避免上架后被大量差评。5.4 国内应用市场上架华为、小米、OPPO 的差异化要求国内渠道审核比 App Store 更侧重合规性华为应用市场必须接入 HMS Coreagconnect-services.json文件需放在android/app/目录隐私政策中需明确说明“华为分析服务”数据收集范围提交时选择“游戏”或“应用”分类错误会导致审核驳回。小米应用商店要求AndroidManifest.xml中android:allowBackupfalse防止用户数据泄露APK 包名必须以com.开头且不能含下划线_。OPPO 应用商店首次启动必须展示《用户协议》和《隐私政策》弹窗且“同意”按钮文字不能为“确定”需提供debug.keystore的 SHA256 指纹用于签名验证。我们编写了自动化脚本publish_china.sh根据渠道参数自动修改build.gradle和AndroidManifest.xml确保一次构建多渠道分发。6. 常见问题排查与实战经验那些文档里不会写的坑6.1 “unable to find suitable Visual Studio toolchain” 错误的根因与 3 种解法这个错误出现在 Windows 上构建 Android 时本质是 Flutter 找不到 MSVC 编译器。根本原因Flutter 3.0 要求 Visual Studio 202217.0而很多开发者安装的是 VS 2019vswhere.exe工具未正确识别 VS 安装路径。解法一推荐重装 Visual Studio 2022下载 Visual Studio Community 2022 安装时勾选 “Desktop development with C” 工作负载运行flutter doctor -v确认Visual Studio显示17.0.0。解法二手动指定工具链路径找到 VS 2022 安装目录通常是C:\Program Files\Microsoft Visual Studio\2022\Community\设置环境变量set VSCMD_VER17.0.0set VCToolsVersion14.30.30705版本号需匹配重启终端后执行flutter build apk。解法三降级 Flutter临时方案fvm install 3.13.9该版本兼容 VS 2019但不推荐长期使用因旧版缺乏新 API 支持。6.2 “You are applying Flutters main Gradle plugin imperatively” 警告的实质与消除方法这是 Gradle 8.0 的警告源于android/app/build.gradle中apply plugin: com.android.application的旧写法。Flutter 3.22 默认使用 Gradle 8.0必须改为声明式插件修改前apply plugin: com.android.application apply plugin: kotlin-android apply from: $flutterRoot/packages/flutter_tools/gradle/flutter.gradle修改后plugins { id com.android.application id org.jetbrains.kotlin.android id dev.flutter.flutter-gradle-plugin }同步修改android/build.gradleplugins { id com.android.application version 8.1.0 apply false id org.jetbrains.kotlin.android version 1.8.0 apply false id dev.flutter.flutter-gradle-plugin version 1.0.0 apply false }注意flutter-gradle-plugin版本必须与 Flutter SDK 版本匹配3.22 对应1.0.0可在flutter/packages/flutter_tools/gradle/flutter.gradle中查证。6.3 iOS 真机调试白屏90% 是 Info.plist 配置遗漏白屏通常意味着 Flutter 引擎未启动。检查ios/Runner/Info.plist的 4 个关键键CFBundleExecutable必须为$(EXECUTABLE_NAME)不能是空字符串LSRequiresIPhoneOS必须为trueUISupportedInterfaceOrientations至少包含UIInterfaceOrientationPortraitUIViewControllerBasedStatusBarAppearance必须为false否则状态栏样式异常导致白屏。用plutil -p ios/Runner/Info.plist命令验证 JSON 格式是否合法避免 XML 语法错误。6.4 Android Studio 报错 “Content is not allowed in prolog” 的 XML 修复指南此错误表明AndroidManifest.xml文件开头有不可见字符如 BOM 头。解决方案用 VS Code 打开android/app/src/main/AndroidManifest.xml查看右下角编码格式若显示UTF-8 with BOM点击切换为UTF-8删除文件开头的?xml version1.0 encodingutf-8?行重新输入确保无空格保存后执行flutter clean flutter pub get。6.5 Flutter 内存泄漏典型场景与devtools定位实战我们曾遇到一个严重泄漏用户连续打开 10 个详情页后内存占用达 1.2GB。用devtools定位步骤启动 App进入内存监控页点击 “Record” 开始录制执行泄漏操作如打开详情页 → 返回 → 重复 5 次点击 “Stop” 结束录制在 Heap Snapshot 中点击 “Class” 标签按实例数排序发现RenderParagraph实例数达 2000远超正常值 100点击该类查看 “Retaining Path”发现TextEditingController被StatefulWidget持有但未在dispose()中controller.dispose()。修复代码class DetailPage extends StatefulWidget { override _DetailPageState createState() _DetailPageState(); } class _DetailPageState extends StateDetailPage { final controller TextEditingController(); override void dispose() { controller.dispose(); // 必须释放 super.dispose(); } }实操心得所有StreamSubscription、Timer、AnimationController、TextEditingController
返回列表