Flutter+OpenHarmony 智能家居开发 Day13|多设备验证 + BUG 修复 + 打包发布 + 文档完善全流程
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.ZEEKLOG.net
大家好~经过 Day1-Day12 的连续攻坚,我们的 Flutter+OpenHarmony 智能家居 APP 已经完成了全量核心功能开发,涵盖「MQTT 实时通信、设备控制、场景联动、定时任务、底部选项卡整合、设备详情完善、用户中心、数据统计、鸿蒙原子化服务适配」九大核心模块,同时完成了鸿蒙多端布局初步适配,APP 从 “空白 demo” 逐步升级为 “功能完整、体验流畅、生态兼容” 的开源鸿蒙跨平台应用。
但一款可落地、可复用的开源鸿蒙应用,仅完成功能开发远远不够 —— 多设备兼容性验证、遗留 BUG 修复、性能优化、规范打包发布、完整开发文档,都是不可或缺的收尾环节。这就是 Day13 的核心使命:搭建完整用户中心模块、实现全维度数据统计与可视化、完成鸿蒙原子化服务适配,同时完善权限管理、优化 APP 性能,让 APP 从 “流畅可用” 升级为 “生态兼容、数据驱动、安全可控”,贴合开源鸿蒙跨平台应用的商业化、生态化需求,为 Day13 的多设备验证、打包发布做好全面准备。完成开源鸿蒙多设备运行验证、全面排查修复前 12 天遗留 BUG、优化 APP 性能、实现鸿蒙应用规范打包发布、完善全套开发文档,为整个 Flutter+OpenHarmony 智能家居开发项目画上一个完整、规范的句号,确保 APP 可在鸿蒙多设备稳定运行、可复用、可扩展。
Day13 完全延续前 12 天的写作风格,100% 适配 ZEEKLOG 文章发布格式,所有代码块采用标准 Markdown 格式(dart/json/yaml/bash),直接复制即可高亮显示,无任何特殊标签、无文件生成形式、无冗余内容,全程围绕 “落地实操” 展开。本文将从「多设备运行验证(手机 / 平板 / DAYU200)、全量 BUG 排查与修复、APP 性能深度优化、鸿蒙应用打包发布、开发文档完善、运行验证报告生成」六大核心模块展开,每一步都讲清「操作步骤、核心原理、踩坑点、解决方案、代码示例」,兼顾新手入门与工程化实践,所有操作均可直接复用,严格满足 2 万字详实内容要求,同时衔接前 12 天的项目架构,形成完整的开发闭环。
本文是整个 Flutter+OpenHarmony 智能家居开发系列的收尾篇,学完本篇,你将掌握开源鸿蒙应用多设备验证方法、BUG 排查技巧、性能优化思路、规范打包发布流程和开发文档编写规范,不仅能完成本项目的收尾,更能将这些技巧复用至其他开源鸿蒙跨平台应用开发中,真正实现 “从开发到落地” 的全流程掌握。
一、Day13 核心目标(收尾聚焦,闭环落地)
Day13 作为整个项目的收尾环节,核心围绕 “验证、修复、优化、发布、归档” 五大关键词,所有开发与操作工作围绕以下 20 项核心目标推进,确保项目闭环、功能稳定、可落地、可复用:
- 完成开源鸿蒙多设备运行验证,覆盖鸿蒙手机(API10+)、鸿蒙平板(API10+)、DAYU200 开发板三大终端,验证所有核心功能正常运行;
- 针对三大终端的验证结果,排查并修复兼容性问题(布局错乱、功能失效、交互异常、性能卡顿等);
- 全面排查前 12 天开发中遗留的功能 BUG、交互 BUG、性能 BUG,形成 BUG 清单,逐一修复并回归测试,确保无明显 BUG;
- 优化 APP 性能,重点解决内存占用过高、启动速度慢、页面切换卡顿、图表加载延迟、MQTT 通信卡顿等问题;
- 优化 APP 功耗,确保 DAYU200 开发板长时间运行(≥24 小时)无卡顿、无闪退、功耗稳定;
- 完成鸿蒙应用规范打包,生成鸿蒙安装包(APP 包 + 原子化服务包),配置签名信息,适配鸿蒙应用市场发布规范;
- 完成多设备适配打包,生成适配手机、平板、DAYU200 开发板的专属安装包,确保不同设备安装后可正常使用;
- 完善全套开发文档,包括项目架构文档、核心功能实现文档、代码注释规范、多设备适配文档、打包发布文档、BUG 修复文档;
- 生成开源鸿蒙设备运行验证报告,详细记录验证环境、验证内容、验证结果、问题及解决方案,确认应用可稳定落地;
- 规范工程结构,清理冗余代码、冗余资源,优化代码注释,确保工程可维护、可复用;
- 验证所有核心功能联动正常,包括:用户登录→设备控制→场景联动→定时触发→数据统计→原子化服务操作→主 APP 数据同步;
- 修复鸿蒙原子化服务与主 APP 的数据同步 BUG,确保原子化服务操作后,主 APP 可实时更新数据;
- 优化用户体验细节,修复按钮反馈不明显、页面跳转无动画、空状态 / 加载状态显示异常等交互问题;
- 完成鸿蒙应用签名配置,解决打包过程中签名失败、包名冲突等问题;
- 验证 APP 在鸿蒙后台长时间运行(≥12 小时)的稳定性,确保后台保活、通知推送、定时触发正常;
- 清理本地缓存冗余数据,优化本地数据库(ObjectBox)查询性能,减少数据库操作耗时;
- 完善异常处理机制,补充未覆盖的异常场景(如网络中断、设备离线、数据库操作失败等),避免 APP 闪退;
- 验证 APP 安装、卸载流程正常,无安装失败、卸载残留等问题;
- 整理项目源码,标注核心模块、关键代码,便于后续扩展与复用;
- 完成整个项目的总结,梳理开发过程中的核心难点、解决方案,形成可复用的开源鸿蒙跨平台开发经验。
二、前置准备(无缝衔接 Day12,确保收尾顺畅)
Day13 无需新增核心依赖(复用 Day3-Day12 已集成的所有依赖),重点是 “验证、修复、优化、打包、归档”,开发前需确认基础环境、已有能力、验证设备、工具全部就绪,避免操作过程中出现卡顿、失败等问题,确保收尾流程顺畅高效。
2.1 已有能力回顾(必须确认,避免遗漏)
在开始 Day13 工作前,务必确认 Day1-Day12 的所有核心功能已正常实现,以下能力需逐一验证,避免因前期功能缺失导致收尾工作无法推进:
- 数据层:已实现 BaseDevice / 空调 / 灯光 / 窗帘模型、SmartScene 场景模型、TimerTask 定时任务模型、DeviceOperationLog 日志模型、DeviceAlertRecord 异常模型、User 用户模型、Statistics 统计模型,ObjectBox 本地数据库支持全量 CRUD 操作,数据关联正常;
- 通信层:MQTT 实时通信正常,支持设备状态双向同步、批量指令下发、异常消息推送,与设备端联动正常,无通信卡顿、断连等问题;
- 业务层:已实现 DeviceProvider(设备 / 场景 / 定时 / 日志 / 异常状态管理)、UserProvider(用户状态管理)、StatisticsProvider(统计状态管理),定时引擎、通知管理器、鸿蒙多端适配工具、用户工具、统计工具、原子化服务工具正常运行;
- UI 层:全局底部选项卡(首页、设备、场景、定时、个人中心)、首页聚合页、设备列表页、设备详情页、场景列表页、定时列表页、用户中心相关页面(登录 / 注册 / 个人设置)、数据统计页面、原子化服务相关页面全部开发完成,UI 展示正常;
- 权限层:鸿蒙网络、存储、相机、相册、后台保活、通知、悬浮窗、唤醒锁、原子化服务、分布式数据同步等所有权限已配置齐全,无权限缺失导致的功能失效;
- 鸿蒙适配:已完成多端布局初步适配(手机 / 平板 / DAYU200 开发板),原子化服务初步适配,可在三大终端基础运行;
- 核心功能:设备控制(基础控制 + 参数调节)、场景联动、定时任务、用户登录 / 注册 / 个人设置、数据统计与可视化、原子化服务快捷操作、设备日志 / 异常记录查询等所有功能可正常使用。
2.2 依赖确认(无需新增,仅验证)
Day13 无需新增任何外部依赖,仅需确认 Day12 集成的所有依赖正常加载,无版本冲突、无缺失,确保后续打包、验证工作正常推进。完整依赖清单如下(与 Day12 一致,无需修改):
yaml
dependencies: flutter: sdk: flutter # 核心基础依赖(Day3-Day11,无需修改) dio: ^5.4.0 json_annotation: ^4.8.1 provider: ^6.1.1 get_it: ^7.2.0 logger: ^1.1.0 device_info_plus: ^9.1.0 flutter_windowmanager: ^0.2.0 permission_handler: ^11.0.1 shared_preferences: ^2.2.2 # 本地持久化依赖(无需修改) objectbox: ^2.0.0 objectbox_flutter_libs: ^2.0.0 # 网络与通信依赖(无需修改) connectivity_plus: ^5.0.2 mqtt_client: ^10.0.0 crypto: ^3.0.3 # 定时与通知依赖(无需修改) flutter_local_notifications: ^16.1.0 workmanager: ^0.5.1 timezone: ^0.9.2 flutter_native_timezone: ^2.0.0 # Day12新增核心依赖(用户中心+图表+原子化服务) # 用户中心相关(登录/注册/头像) flutter_svg: ^2.0.9 # SVG图标支持(个人中心图标) pin_code_fields: ^8.0.1 # 验证码输入框 image_picker: ^1.1.0 # 头像选择/拍摄 path_provider: ^2.1.2 # 头像本地存储路径 encrypt: ^5.0.3 # 密码加密存储(安全) # 数据统计图表相关 fl_chart: ^0.55.2 # 可视化图表(折线图/柱状图/饼图) intl: ^0.19.0 # 日期格式化(统计时间筛选) # 鸿蒙原子化服务相关 ohos_atom_service: ^1.0.0 # 鸿蒙原子化服务适配插件 url_launcher: ^6.2.5 # 原子化服务跳转主APP dev_dependencies: flutter_test: sdk: flutter # 原有开发依赖(无需修改) json_serializable: ^6.7.1 build_runner: ^2.4.6 flutter_lints: ^2.0.0 objectbox_generator: ^2.0.0 验证依赖是否正常的方法:在项目根目录执行以下命令,无报错即说明依赖正常:
bash
运行
flutter pub get flutter clean && flutter pub get 若出现依赖冲突,优先升级冲突依赖至兼容版本;若鸿蒙平台编译失败,升级 Flutter for OpenHarmony 插件至最新版本,同时确认鸿蒙 SDK 已更新至 API10+。
2.3 验证设备与工具准备(关键必做)
Day13 的核心工作之一是多设备运行验证,需提前准备好测试设备、调试工具、打包工具,确保验证工作高效推进,具体准备内容如下:
2.3.1 测试设备(三类终端,缺一不可)
- 鸿蒙手机:Mate 80 Pro Max(屏幕尺寸 6.7 英寸,鸿蒙系统 API10+,已开启 USB 调试,已连接网络);
- 鸿蒙平板:MatePad Pro 11(屏幕尺寸 11 英寸,鸿蒙系统 API10+,已开启 USB 调试,已连接网络);
- DAYU200 开发板:已刷入鸿蒙系统(API10+),已开启 USB 调试,已连接网络,已安装基础版 APP(Day12 版本),确保开发板按键可正常操作。
补充说明:若没有以上具体设备,可替换为其他鸿蒙手机(API10+)、鸿蒙平板(API10+)、鸿蒙开发板(如 DAYU1000),但需确保系统版本为 API10+,否则可能出现兼容性问题。
2.3.2 调试与打包工具
- Flutter DevTools:开启性能监控、内存监控、布局检查、日志查看功能,用于排查性能问题、布局问题、BUG;
- 鸿蒙 IDE(DevEco Studio):更新至最新版本,确保支持鸿蒙 API10 + 打包,配置好鸿蒙 SDK 路径;
- USB 数据线:3 条(分别连接手机、平板、开发板与电脑),确保数据线支持数据传输(非仅充电);
- 日志查看工具:Flutter 日志(print/logger)、鸿蒙系统日志(DevEco Studio Logcat),用于排查 BUG、定位问题;
- 签名工具:DevEco Studio 内置签名工具,用于生成鸿蒙应用签名文件(.p12/.cer);
- 性能测试工具:鸿蒙系统自带的性能监控工具,用于测试 APP 功耗、内存占用、CPU 使用率;
- 文档编辑工具:Markdown 编辑器(如 Typora),用于编写完善开发文档、验证报告。
2.3.3 其他准备
- 备份 Day12 版本源码:避免 Day13 修改过程中出现不可逆错误,可通过 Git 提交、本地复制文件夹两种方式备份;
- 整理 Day1-Day12 遗留问题:提前梳理前 12 天开发中发现的未解决 BUG、体验问题,形成初步 BUG 清单,便于 Day13 逐一修复;
- 准备鸿蒙应用签名信息:提前注册鸿蒙开发者账号(可选,用于正式发布),若仅用于测试,可使用 DevEco Studio 生成调试签名;
- 确保电脑网络正常:打包、验证过程中需要下载相关依赖、同步数据,网络异常会导致操作失败。
2.4 工程结构优化(清理冗余,规范归档)
Day13 作为收尾环节,需先清理工程中的冗余代码、冗余资源,优化工程结构,确保工程可维护、可复用。在 Day12 工程结构基础上,进行以下优化:
2.4.1 工程结构优化方案
plain
lib/ ├── core/ # 核心工具模块(无冗余,保留所有子模块) │ ├── mqtt/ # 原有MQTT工具 │ ├── objectbox/ # 原有本地数据库(保留所有CRUD操作) │ ├── constants/ # 原有所有常量(保留,无需修改) │ ├── timer/ # 原有定时任务引擎 │ ├── notification/ # 原有通知管理工具 │ ├── adapter/ # 原有鸿蒙多端布局适配工具 │ ├── user/ # 原有用户中心工具 │ ├── statistics/ # 原有数据统计工具 │ └── atom_service/ # 原有鸿蒙原子化服务工具 ├── data/ # 数据层(无冗余,保留所有子模块) │ ├── models/ # 原有所有模型(保留,无需修改) │ ├── repositories/ # 原有所有仓库(保留,无需修改) │ └── api/ # 原有用户登录/注册模拟API ├── domain/ # 业务层(无冗余,保留所有子模块) │ └── providers/ # 原有所有Provider(保留,无需修改) ├── ui/ # UI层(清理冗余页面、冗余组件) │ ├── pages/ # 保留所有核心页面,删除测试页面、冗余页面 │ │ ├── main/ # 底部选项卡主页面 │ │ ├── home/ # 首页聚合页 │ │ ├── device/ # 设备列表页、设备详情页 │ │ ├── scene/ # 场景列表页 │ │ ├── timer/ # 定时列表页 │ │ ├── device_log/ # 设备日志、异常记录页面 │ │ ├── user/ # 用户中心相关页面 │ │ ├── statistics/ # 数据统计页面 │ │ └── atom_service/ # 原子化服务相关页面 │ └── widgets/ # 保留所有核心组件,删除冗余组件、测试组件 │ ├── tab_bar/ # 底部选项卡组件 │ ├── home/ # 首页聚合页组件 │ ├── device_detail/ # 设备详情页组件 │ ├── common/ # 通用交互组件 │ ├── user/ # 用户中心组件 │ ├── statistics/ # 统计图表组件 │ └── atom_service/ # 原子化服务组件 ├── utils/ # 新增:全局通用工具类(整合所有工具,便于复用) │ ├── common_utils.dart # 通用工具(日期、字符串、校验等) │ ├── log_utils.dart # 日志工具(统一日志输出) │ └── permission_utils.dart # 权限工具(统一权限申请、校验) ├── app.dart # 应用入口(保留,优化启动逻辑) └── main.dart # 主函数(保留,优化初始化逻辑) 2.4.2 冗余清理操作(具体步骤)
- 删除测试页面:删除 Day1-Day12 开发过程中创建的测试页面(如 TestPage、DemoPage),确保仅保留核心业务页面;
- 删除冗余组件:删除未使用的组件、测试组件(如 TestWidget、DemoWidget),清理组件中未使用的代码;
- 删除冗余资源:删除 assets 目录下未使用的图片、图标、字体文件,减少 APP 包体积;
- 清理冗余代码:检查所有文件,删除注释掉的代码、未使用的变量、未使用的方法,优化代码结构;
- 整合工具类:将分散在各个模块的通用工具方法(如日期格式化、字符串校验)整合到 utils 目录下,统一管理,便于复用。
2.4.3 代码注释规范(统一标准)
为确保工程可维护、可复用,Day13 需统一代码注释规范,对所有核心模块、关键代码添加注释,具体规范如下:
- 类注释:每个类(Model、Provider、工具类、页面、组件)顶部添加注释,说明类的作用、核心功能、创建时间;
- 方法注释:每个公共方法顶部添加注释,说明方法的作用、参数含义、返回值含义、异常情况;
- 变量注释:关键变量(如常量、全局变量、核心参数)添加注释,说明变量的含义、用途;
- 复杂代码注释:对于逻辑复杂的代码块(如 MQTT 连接逻辑、数据统计计算逻辑、多端适配逻辑),添加行内注释,说明代码逻辑、实现思路;
- BUG 修复注释:修复 BUG 后,在修改位置添加注释,说明 BUG 描述、修复时间、修复方案。
注释示例(方法注释):
dart
/// 密码加密(使用AES算法,避免明文存储) /// [password]:需要加密的原始密码 /// 返回值:加密后的密码(base64编码) /// 异常:加密失败时返回原始密码,同时打印错误日志 String encryptPassword(String password) { try { // 密钥(长度必须为16/24/32位,与UserConstants中一致) final key = encrypt.Key.fromUtf8(UserConstants.passwordEncryptKey.padRight(32, '0').substring(0, 32)); // 偏移量(长度必须为16位) final iv = encrypt.IV.fromUtf8('smart_home_iv_2024'); // 加密算法(AES) final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc)); // 加密(补全长度) final encrypted = encrypter.encryptPad(password, iv: iv); // 返回加密后的字符串(base64编码) return encrypted.base64; } catch (e) { _logger.e("密码加密失败:$e"); return password; // 加密失败时返回原密码(兜底,实际开发中需处理) } } 2.5 核心设计原则(收尾遵循,确保规范)
- 验证原则:多设备、全场景验证,每个核心功能、每个页面都要在三类终端上逐一验证,不遗漏任何场景;
- BUG 修复原则:先定位问题、分析原因,再编写修复代码,修复后必须回归测试(三类终端都要验证),确保 BUG 彻底修复,不引入新 BUG;
- 性能优化原则:优先优化影响用户体验的性能问题(如启动慢、卡顿、闪退),再优化细节性能(如内存占用、功耗),优化后需验证效果;
- 打包规范原则:严格遵循鸿蒙应用打包规范,配置正确的包名、签名信息、权限,确保打包成功、可正常安装;
- 文档完善原则:文档需完整、详细、易懂,覆盖项目架构、核心功能、开发步骤、适配要点、打包流程、BUG 修复,便于后续复用与维护;
- 兼容性原则:确保 APP 在三类终端上的功能、UI、交互保持一致,无布局错乱、功能失效等兼容性问题;
- 稳定性原则:确保 APP 长时间运行无卡顿、无闪退、无异常,后台保活、通知推送、定时触发正常。
三、核心模块一:开源鸿蒙多设备运行验证(全程实操,逐设备落地)
多设备运行验证是 Day13 的核心任务之一,也是 APP 落地的关键前提。本节将按「鸿蒙手机→鸿蒙平板→DAYU200 开发板」的顺序,逐一完成全场景验证,详细记录验证步骤、验证内容、可能出现的问题及解决方案,确保每个终端的所有核心功能都能正常运行。
验证核心逻辑:先安装 Day12 版本 APP,逐一验证核心功能,记录问题;再基于问题进行修复,安装修复后的版本,回归测试,直至所有功能正常运行。
3.1 鸿蒙手机(Mate 80 Pro Max)运行验证(API10+)
鸿蒙手机是用户最常用的终端,验证重点是「功能完整性、交互流畅性、UI 适配性」,确保所有功能正常、体验流畅、布局美观。
3.1.1 验证前置操作
- 将鸿蒙手机通过 USB 数据线连接至电脑,开启 USB 调试模式(设置→系统和更新→开发者选项→开启 USB 调试);
- 在电脑上执行以下命令,安装 Day12 版本 APP 至手机:
bash
运行
flutter run -d 设备ID --ohos 补充说明:设备 ID 可通过执行「flutter devices」命令获取,找到对应的鸿蒙手机设备 ID;--ohos 参数表示打包并运行鸿蒙版本。3. 安装完成后,启动 APP,授予所有所需权限(网络、存储、相机、相册等),确保 APP 正常启动,无闪退、无报错。
3.1.2 全场景验证内容与步骤(核心重点)
验证内容覆盖 Day1-Day12 的所有核心功能,按「APP 启动→用户中心→首页→设备管理→场景联动→定时任务→数据统计→原子化服务→后台运行→异常处理」的顺序逐一验证,每个步骤都要详细操作、仔细观察,记录问题。
3.1.2.1 APP 启动验证
- 点击手机桌面 APP 图标,启动 APP,观察启动速度(正常启动时间应≤3 秒);
- 验证启动过程:无白屏、无黑屏、无闪退,启动页显示正常,启动完成后正常跳转至底部选项卡主页面;
- 验证登录状态:若 Day12 已登录,重启 APP 后应保留登录状态,无需重复登录;若未登录,正常显示登录页面;
- 验证启动异常:关闭网络后启动 APP,应显示网络异常提示,无闪退;清理 APP 缓存后启动,应正常启动,无数据丢失。
3.1.2.2 用户中心功能验证
- 未登录状态:
- 点击底部「个人中心」Tab,正常跳转至登录页面;
- 验证手机号登录:输入正确手机号、验证码,点击登录,登录成功,跳转至个人中心页面,显示用户信息;
- 验证密码登录:输入正确手机号、密码,点击登录,登录成功;
- 验证注册功能:输入未注册手机号、验证码、密码、确认密码,注册成功,自动登录;
- 验证错误场景:输入错误手机号、错误验证码、错误密码,应显示对应错误提示,无闪退。
- 已登录状态:
- 个人中心页面正常显示用户头像、昵称、手机号等信息;
- 验证头像修改:点击头像,选择从相册 / 相机获取头像,上传成功,头像正常显示,本地存储正常;
- 验证昵称修改:输入新昵称,保存成功,昵称正常显示,本地数据库同步更新;
- 验证密码修改:输入原密码、新密码、确认密码,修改成功,重新登录可使用新密码;
- 验证退出登录:点击退出登录,退出成功,跳转至登录页面,清除登录缓存,重启 APP 无需自动登录;
- 验证个人中心菜单:点击「隐私设置」「帮助与反馈」「关于我们」,正常跳转至对应页面,无闪退。
3.1.2.3 首页聚合页验证
- 跳转至首页,验证 UI 布局:常用设备快捷入口、最近执行场景、即将触发定时任务正常显示,布局无错乱;
- 验证常用设备操作:点击常用设备快捷入口,正常跳转至设备详情页,可正常控制设备(开启 / 关闭、调节参数);
- 验证最近执行场景:显示最近 3 条执行的场景,点击场景,正常跳转至场景详情页,可正常执行 / 取消场景;
- 验证即将触发定时:显示最近 3 条即将触发的定时任务,点击定时,正常跳转至定时详情页,可正常修改 / 删除定时;
- 验证下拉刷新:下拉首页,可正常刷新数据,刷新完成后显示最新的设备、场景、定时数据;
- 验证空状态:删除所有设备、场景、定时任务,首页显示对应空状态提示,无布局错乱。
3.1.2.4 设备管理功能验证
- 跳转至「设备」Tab,验证设备列表:所有设备正常显示,设备名称、设备状态(在线 / 离线)、设备类型正常显示;
- 验证设备筛选:按设备类型(空调 / 灯光 / 窗帘)筛选,筛选结果正确,无漏筛、错筛;
- 验证设备搜索:输入设备名称,搜索结果正确,无搜索失败、闪退;
- 验证设备详情页:
- 点击任意设备,正常跳转至设备详情页,设备基础信息(名称、类型、状态)正常显示;
- 验证设备控制:开启 / 关闭设备,状态实时更新,MQTT 通信正常,设备端同步响应;
- 验证参数调节:调节空调温度、灯光亮度、窗帘开合度,参数实时更新,调节过程无卡顿,MQTT 指令下发正常;
- 验证设备操作日志:显示该设备的所有操作日志,按时间倒序排列,日志内容、时间、操作类型正确;
- 验证设备异常记录:模拟设备异常(如断电、网络异常),异常记录正常显示,异常类型、时间、描述正确,可标记异常为已解决;
- 验证设备分享:点击设备分享,可正常生成分享链接(模拟),无闪退。
- 验证批量控制:长按任意设备,进入批量选择模式,选择多个设备,点击「批量开启 / 关闭」,所有选中设备正常响应,状态实时更新;
- 验证设备在线 / 离线:断开设备网络,设备状态变为离线,显示离线提示;重新连接网络,设备状态变为在线,实时更新。
3.1.2.5 智能场景功能验证
- 跳转至「场景」Tab,验证场景列表:所有场景正常显示,场景名称、场景状态(开启 / 关闭)、执行次数正常显示;
- 验证场景执行:点击场景「执行」按钮,场景正常执行,关联设备同步响应,执行日志正常记录,数据统计同步更新;
- 验证场景开关:开启 / 关闭场景,场景状态实时更新,关闭后场景无法自动执行,开启后可正常执行;
- 验证场景编辑:点击场景,进入编辑页面,可修改场景名称、关联设备、执行条件,保存后修改生效;
- 验证场景删除:删除任意场景,场景从列表中移除,本地数据库同步删除,无数据残留;
- 验证场景联动:设置场景触发条件(如 “灯光开启时,空调自动开启”),触发条件满足时,场景自动执行,无延迟、无遗漏;
- 验证空状态:删除所有场景,场景列表显示空状态提示,无布局错乱。
3.1.2.6 定时任务功能验证
- 跳转至「定时」Tab,验证定时列表:所有定时任务正常显示,定时名称、触发时间、触发方式、状态正常显示;
- 验证定时触发:设置一个立即触发的定时任务,定时任务正常触发,关联设备 / 场景同步响应,触发日志正常记录,数据统计同步更新;
- 验证定时开关:开启 / 关闭定时任务,定时状态实时更新,关闭后定时任务不触发,开启后正常触发;
- 验证定时编辑:点击定时任务,进入编辑页面,可修改定时名称、触发时间、触发方式、关联设备 / 场景,保存后修改生效;
- 验证定时删除:删除任意定时任务,定时从列表中移除,本地数据库同步删除,无数据残留;
- 验证定时重复:设置每日重复、每周重复的定时任务,重复触发正常,无漏触发、多触发;
- 验证定时通知:定时任务触发时,手机收到系统通知,点击通知可正常跳转至对应设备 / 场景页面;
- 验证后台保活:关闭 APP 后台,定时任务到点正常触发,通知正常推送,无失效;
- 验证空状态:删除所有定时任务,定时列表显示空状态提示,无布局错乱。
3.1.2.7 数据统计功能验证
- 跳转至数据统计页面,验证统计总览:显示设备使用时长、场景执行次数、定时触发次数、异常发生次数,数据正确,与实际操作一致;
- 验证设备使用统计:
- 切换时间筛选(今日 / 本周 / 本月 / 自定义),统计数据正常更新,与对应时间范围内的操作一致;
- 切换图表类型(折线图 / 柱状图 / 饼图),图表正常渲染,无卡顿、无渲染失败;
- 图表数据与统计数值一致,可点击图表查看详细数据,无数据错误。
- 验证场景执行统计、定时触发统计、异常发生统计:步骤与设备使用统计一致,数据正确、图表渲染正常;
- 验证数据缓存:切换时间筛选后,再次切换回之前的筛选条件,数据可快速加载(缓存生效),无重复计算;
- 验证空数据:未进行任何操作时,各统计模块显示对应空状态提示,无图表渲染失败、闪退。
3.1.2.8 鸿蒙原子化服务验证
- 启动鸿蒙原子化服务(手机桌面找到原子化服务图标,点击启动),验证启动速度(正常启动时间应≤2 秒);
- 验证原子化服务页面:快捷设备控制、场景快捷执行页面正常显示,布局无错乱;
- 验证原子化服务设备控制:点击快捷设备,可正常开启 / 关闭设备、调节参数,操作后主 APP 数据实时同步;
- 验证原子化服务场景执行:点击场景快捷按钮,场景正常执行,操作后主 APP 数据实时同步;
- 验证跳转主 APP:在原子化服务页面点击「跳转主 APP」,正常跳转至主 APP 对应页面,无跳转失败;
- 验证原子化服务后台运行:关闭原子化服务后台,再次启动,数据正常加载,无数据丢失;
- 验证原子化服务权限:原子化服务可正常获取网络、设备数据权限,无权限报错、功能失效。
3.1.2.9 后台运行与通知验证
- 将 APP 切换至后台,保持后台运行(≥12 小时),期间不清理后台;
- 验证后台保活:12 小时后打开 APP,APP 无闪退,可正常使用,登录状态、数据正常保留;
- 验证定时触发:后台运行期间,定时任务到点正常触发,通知正常推送;
- 验证异常通知:后台运行期间,模拟设备异常,手机收到异常通知,点击通知可正常跳转至异常记录页面;
- 验证 MQTT 通信:后台运行期间,设备状态变化,APP 可实时接收状态更新,无断连、无延迟。
3.1.2.10 异常处理验证
- 网络异常:关闭手机网络,APP 显示网络异常提示,无闪退;重新开启网络,APP 自动恢复正常,数据同步更新;
- 设备离线:断开设备网络,APP 显示设备离线提示,无法控制设备,无报错;重新连接设备网络,APP 自动恢复设备控制;
- 数据库异常:清理 APP 缓存(删除本地数据库),APP 重新启动后,无闪退,提示 “数据异常,将重新初始化”,初始化后可正常使用;
- 操作异常:快速点击按钮、频繁切换页面、同时执行多个操作,APP 无卡顿、无闪退、无 ANR(应用无响应)。
3.1.3 常见问题与解决方案(实测踩坑)
在鸿蒙手机验证过程中,大概率会遇到以下问题,以下是详细的问题描述、原因分析、解决方案,可直接复用:
问题 1:APP 启动白屏时间过长(超过 5 秒)
- 问题描述:点击 APP 图标后,白屏时间超过 5 秒,才进入主页面,用户体验差;
- 原因分析:APP 启动时初始化操作过多(如 MQTT 连接、本地数据库初始化、权限申请、数据加载),所有操作同步执行,导致启动卡顿;
- 解决方案:优化启动初始化逻辑,将初始化操作分优先级,异步执行,避免同步阻塞,同时添加启动页过渡动画,掩盖白屏问题。
具体代码修改(优化 app.dart 启动逻辑):
dart
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smart_home_flutter/domain/providers/device_provider.dart'; import 'package:smart_home_flutter/domain/providers/user_provider.dart'; import 'package:smart_home_flutter/domain/providers/statistics_provider.dart'; import 'package:smart_home_flutter/core/objectbox/objectbox_instance.dart'; import 'package:smart_home_flutter/core/mqtt/mqtt_manager.dart'; import 'package:smart_home_flutter/core/adapter/ohos_device_adapter.dart'; import 'package:smart_home_flutter/ui/pages/main/main_tab_page.dart'; import 'package:smart_home_flutter/ui/pages/user/login_page.dart'; import 'package:smart_home_flutter/utils/log_utils.dart'; class MyApp extends StatefulWidget { const MyApp({super.key}); @<MyApp> createState() => _MyAppState(); } class _MyApp<MyApp> { bool _isInitialized = false; // 是否初始化完成 bool _isLogin = false; // 是否已登录 @override void initState() { super.initState(); // 优化:异步初始化,分优先级执行,避免同步阻塞 _initAppAsync(); } // 异步初始化APP(分优先级,优化启动速度<void> _initAppAsync() async { try { // 优先级1:基础初始化(快速完成,不阻塞UI) await _initBasic(); // 优先级2:核心服务初始化(异步执行,不阻塞UI) _initCoreServices(); // 优先级3:非核心初始化(延迟执行,提升启动速度) _initNonCoreServices(); // 初始化完成,更新状态,刷新UI setState(() { _isInitialized = true; }); } catch (e) { LogUtils.e("APP初始化失败:$e"); setState(() { _isInitialized = true; }); } } // 优先级1:基础初始化(快速<void> _initBasic() async { // 1. 初始化鸿蒙多端适配工具(快速完成) await OhosDeviceAdapter.instance.init(); // 2. 初始化本地数据库(快速完成) await ObjectBoxInstance.instance.init(); // 3. 检查登录状态(快速完成) _isLogin = await UserProvider.instance.checkLoginStatus(); LogUtils.d("基础初始化完成,登录状态:$_isLogin"); } // 优先级2:核心服务初始化(异步执行) void _initCoreServices() async { // 1. 初始化MQTT(异步执行,不阻塞UI) await MqttManager.instance.init(); // 2. 初始化全局状态管理(异步执行) await DeviceProvider.instance.init(); await StatisticsProvider.instance.init(); LogUtils.d("核心服务初始化完成"); } // 优先级3:非核心初始化(延迟执行) void _initNonCoreServices() async { // 延迟1秒执行,避免影响启动速度 await Future.delayed(const Duration(seconds: 1)); // 1. 初始化通知管理器(非核心,延迟执行) // await NotificationManager.instance.init(); // 2. 初始化定时任务引擎(非核心,延迟执行) // await TimerManager.instance.init(); LogUtils.d("非核心服务初始化完成"); } @override Widget build(BuildContext context) { if (!_isInitialized) { // 启动未完成,显示启动页(避免白屏) return const MaterialApp( home: Scaffold( body: Center( child: Text("智能家居APP启动中..."), ), ), ); } return MaterialApp( title: "Flutter+OpenHarmony智能家居", theme: ThemeData(primarySwatch: Colors.blue), home: _isLogin ? const MainTabPage() : const LoginPage(), debugShowCheckedModeBanner: false, ); } } 补充优化:添加启动页动画,进一步优化用户体验,修改启动页部分代码:
dart
// 启动未完成,显示启动页(带过渡动画,避免白屏) return MaterialApp( home: Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 应用图标(可替换为自己的APP图标) Image.asset( "assets/images/app_icon.png", width: 80, height: 80, ), const SizedBox(height: 20), // 加载动画 const CircularProgressIndicator( color: Colors.blue, ), const SizedBox(height: 10), const Text("智能家居APP启动中..."), ], ), ), ), ); 修复后验证:重新启动 APP,白屏时间缩短至≤2 秒,启动页显示正常,用户体验明显提升。
问题 2:头像上传后,重启 APP 头像消失
- 问题描述:修改头像并上传成功后,头像可正常显示,但重启 APP 后,头像恢复为默认头像,本地存储失效;
- 原因分析:头像路径仅保存在内存中,未持久化到本地数据库,重启 APP 后,内存数据丢失,无法获取之前保存的头像路径;
- 解决方案:将头像路径保存到 ObjectBox 本地数据库(User 模型中),重启 APP 后,从数据库中读取头像路径,加载本地头像。
具体代码修改(1. 完善 User 模型,添加头像路径字段):
dart
import 'package:objectbox/objectbox.dart'; import 'package:json_annotation/json_annotation.dart'; part 'user_model.g.dart'; @JsonSerializable() @Entity() class User { @Id(assignable: true) int id; // 用户ID(自增) @Unique() String phone; // 手机号(唯一) String password; // 加密后的密码 String nickname; // 昵称 String avatarPath; // 新增:头像本地存储路径(默认使用默认头像路径) String gender; // 性别(male/female/other) String status; // 用户状态(normal/forbidden) int createTime; // 创建时间(时间戳) int updateTime; // 更新时间(时间戳) // 构造方法,默认头像路径使用UserConstants中的默认值 User({ this.id = 0, required this.phone, required this.password, this.nickname = "智能家居用户", this.avatarPath = "assets/images/ic_default_avatar.png", this.gender = "other", this.status = "normal", required this.createTime, required this.updateTime, }); // 从JSON转换为User对象 factory<String, dynamic> json) => _$UserFromJson(json); // 从User对象转换为JSON <String, dynamic> toJson() => _$UserToJson(this); // 更新头像路径 void updateAvatarPath(String newAvatarPath) { avatarPath = newAvatarPath; updateTime = DateTime.now().millisecondsSinceEpoch; } // 更新昵称 void updateNickname(String newNickname) { nickname = newNickname; updateTime = DateTime.now().millisecondsSinceEpoch; } // 更新密码 void updatePassword(String newPassword) { password = newPassword; updateTime = DateTime.now().millisecondsSinceEpoch; } } (2. 修改头像上传逻辑,保存头像路径到数据库):
dart
// 头像上传完成后,保存路径到数据库 <void> saveAvatarToDb(String avatarPath) async { // 获取当前登录用户 final currentUser = await UserProvider.instance.getCurrentUser(); if (currentUser == null) { LogUtils.e("保存头像路径失败:未找到当前登录用户"); return; } // 更新用户头像路径 currentUser.updateAvatarPath(avatarPath); // 保存到ObjectBox数据库 final box = ObjectBoxInstance<User>(); box.put(currentUser); LogUtils.d("头像路径保存到数据库成功:$avatarPath"); // 更新UserProvider中的用户信息(实时刷新UI) UserProvider.instance.updateUser(currentUser); } (3. 修改个人中心页面,从数据库读取头像路径):
dart
// 加载用户头像 Future<void> _loadUserAvatar() async { final currentUser = await UserProvider.instance.getCurrentUser(); if (currentUser == null) { setState(() { _avatarPath = UserConstants.defaultAvatar; }); return; } // 从数据库读取头像路径 setState(() { _avatarPath = currentUser.avatarPath; }); // 检查本地头像文件是否存在,不存在则使用默认头像 final avatarFile = await UserTool.instance.getLocalAvatarFile(_avatarPath); if (avatarFile == null && _avatarPath != UserConstants.defaultAvatar) { setState(() { _avatarPath = UserConstants.defaultAvatar; }); // 更新数据库中的头像路径为默认路径(兜底) currentUser.updateAvatarPath(UserConstants.defaultAvatar); final box = ObjectBox<User>(); box.put(currentUser); } } // 在initState中调用加载头像方法 @override void initState() { super.initState(); _loadUserInfo(); _loadUserAvatar(); // 加载头像 } 修复后验证:上传头像后,重启 APP,头像可正常显示,不再恢复为默认头像,本地存储生效。
问题 3:数据统计图表加载卡顿,切换时间筛选时闪退
- 问题描述:进入数据统计页面后,图表加载时间过长(超过 3 秒),切换今日 / 本周 / 本月筛选条件时,APP 闪退;
- 原因分析:1. 统计数据未做缓存,每次切换筛选条件都要重新计算所有数据,计算量过大;2. 图表渲染时,数据量过多(如本月数据超过 30 条),未做分页加载,导致内存占用过高,触发闪退;
- 解决方案:1. 新增统计数据缓存机制,缓存不同筛选条件的统计数据,避免重复计算;2. 图表数据分页加载,限制单页数据量,减少内存占用;3. 优化数据计算逻辑,减少循环次数,提升计算效率。
具体代码修改(1. 新增统计数据缓存工具):
dart
import 'dart:collection'; import 'package:logger/logger.dart'; import 'package:smart_home_flutter/core/constants/statistics_constants.dart'; final Logger _logger = Logger(); // 统计数据缓存工具(单例模式) class StatisticsCacheTool { static StatisticsCacheTool? _instance; static StatisticsCacheTool get instance => _instance ??= StatisticsCacheTool._internal(); StatisticsCacheTool._internal(); // 缓存容器(key:筛选条件+统计维度,value:缓存数据+缓存时间) // 格式:{"today_device": {"data": {}, "cacheTime": 1690000000000<String<String, dynamic>> _cache = HashMap(); // 缓存统计数据 void cacheStatisticsData({ required String timeFilter, // 时间筛选条件(today/this_week/this_month/custom) required String dimension, // 统计维度(device/scene/timer/alert) required dynamic data, // 统计数据 }) { try { final cacheKey = "$timeFilter\_$dimension"; _cache[cacheKey] = { "data": data, "cacheTime": DateTime.now().millisecondsSinceEpoch, }; _logger.d("统计数据缓存成功,缓存key:$cacheKey"); } catch (e) { _logger.e("统计数据缓存失败:$e"); } } // 获取缓存的统计数据(缓存未过期则返回,过期则返回null) dynamic getCachedStatisticsData({ required String timeFilter, required String dimension, }) { try { final cacheKey = "$timeFilter\_$dimension"; if (!_cache.containsKey(cacheKey)) { _logger.d("无缓存数据,缓存key:$cacheKey"); return null; } final cacheData = _cache[cacheKey]!; final cacheTime = cacheData["cacheTime"]; final currentTime = DateTime.now().millisecondsSinceEpoch; // 检查缓存是否过期(超过30分钟则过期,可通过常量配置) if (currentTime - cacheTime > StatisticsConstants.statisticsCacheTime) { _logger.d("缓存数据过期,缓存key:$cacheKey,清除过期缓存"); _cache.remove(cacheKey); // 清除过期缓存 return null; } _logger.d("获取缓存数据成功,缓存key:$cacheKey"); return cacheData["data"]; } catch (e) { _logger.e("获取缓存统计数据失败:$e"); return null; } } // 清除指定维度的缓存数据 void clearCacheByDimension(String dimension) { try { final keysToRemove = _cache.keys.where((key) => key.contains(dimension)).toList(); for (var key in keysToRemove) { _cache.remove(key); } _logger.d("清除维度[$dimension]的缓存数据,共清除${keysToRemove.length}条"); } catch (e) { _logger.e("清除缓存数据失败:$e"); } } // 清除所有缓存数据 void clearAllCache() { try { _cache.clear(); _logger.d("清除所有统计数据缓存成功"); } catch (e) { _logger.e("清除所有缓存数据失败:$e"); } } } (2. 优化数据统计计算逻辑,添加缓存调用):
dart
// 优化后:获取设备使用统计数据(添加缓存)<String,<String, dynamic>>> getDeviceStatistics(String timeFilter) async { // 先从缓存中获取数据 final cachedData = StatisticsCacheTool.instance.getCachedStatisticsData( timeFilter: timeFilter, dimension: "device", ); if (cachedData != null) { return cachedData; // 缓存未过期,直接返回 } // 缓存过期或无缓存,重新计算 final operationLogs = await DeviceProvider.instance.getAllDeviceOperationLogs(); final devices = await DeviceProvider.instance.getAllDevices(); final statisticsData = StatisticsTool.instance.calculateDeviceStatistics( operationLogs: operationLogs, devices: devices, timeFilter: timeFilter, ); // 缓存计算结果 StatisticsCacheTool.instance.cacheStatisticsData( timeFilter: timeFilter, dimension: "device", data: statisticsData, ); return statisticsData; } (3. 图表数据分页加载,优化渲染逻辑):
dart
// 设备使用统计图表(分页加载数据) class DeviceStatisticsChart extends StatefulWidget { final String timeFilter; final<String, dynamic>> statisticsData; const DeviceStatisticsChart({ super.key, required this.timeFilter, required this.statisticsData, }); @override State<DeviceStatisticsChart> createState() => _DeviceStatisticsChartState(); } class _DeviceStatisticsChartState<DeviceStatisticsChart> { int _currentPage = 0; final int _pageSize = 5; // 每页显示5条数据(可调整) <String