首页 / 资讯中心 / 文章详情

Flutter跨平台开发实战:鸿蒙适配洗衣房打卡应用全流程

Flutter跨平台开发实战:鸿蒙适配洗衣房打卡应用全流程 ★ FEATURED ARTICLE
大学宿舍楼的洗衣房永远是一场没有硝烟的战争。高峰期洗衣机全满有人把衣服丢进去就忘了取后边排队的人只能干瞪眼。某高校的洗衣房负责人找过来说想做一个打卡小应用——让同学用手机记录洗衣机的使用开始和结束时间后台能看到每台机器的忙碌状态。我接这个需求时第一反应就是用 Flutter 做跨平台开发顺手把鸿蒙HarmonyOS的适配也一起做了于是就有了这个校园洗衣房打卡应用。这篇文章就把整个开发过程从头到尾讲一遍适合有 Flutter 基础的读者拿来练手也适合想了解鸿蒙适配流程的开发者参考。1. 项目剖析与方案选型1.1 一句话说清需求把需求翻译成技术语言其实很简单一个带状态流转的记录工具。学生拿手机打开应用看到所有洗衣机的位置和当前状态选择一台空闲的机器开始打卡等衣服洗完再点结束打卡。管理员能看到每台机器一天之内被使用了多少次、平均每次用多久从而决定要不要调整洗衣房的开放时间、增加机器数量。这个场景里有两种角色、三类核心用例。学生侧是查状态、开始打卡、结束打卡管理员侧是看统计、看实时状态。整体数据量不大并发量也不高一个洗衣房最多几十台机器几百个学生同时在线已经算极限情况。所以技术选型不需要搞太重的东西重点放在开发效率和设备覆盖面上。1.2 为什么选 Flutter 而不是原生双端校园环境里学生的手机五花八门安卓、iOS、鸿蒙都有让某个方向的同学只能用特定系统的应用根本不现实。如果分别用两套原生代码开发一个小工具项目要养两个技术栈维护成本直接翻倍。Flutter 的核心优势是自绘 UI 引擎。同一套 Dart 代码编译后安卓和 iOS 上渲染出来的界面几乎完全一致不像 WebView 套壳那样受系统 WebView 版本影响。洗衣房打卡这种应用界面不复杂不需要调用太多平台独有能力Flutter 完全镇得住。适配鸿蒙则是另一个维度的考虑。校园里使用鸿蒙设备的比例这两年明显上升很多学生换新手机后直接就是鸿蒙系统。如果应用只能在安卓和 iOS 上跑会白白丢掉一大批用户。去年开始社区已经提供了 Flutter 对鸿蒙的适配支持这套代码不用重写只要按适配分支的流程走一遍就能构建出鸿蒙安装包。这也是跨平台框架最有价值的地方一次编码多端分发。1.3 项目技术栈实际动手之前先把技术栈列清楚后面每一步都是围绕这张表展开的。类别选型理由UI 框架Flutter Dart一套代码跑三个平台自绘引擎保证一致性本地存储sqflite基于 SQLite轻量可靠适合记录类数据状态管理Provider官方推荐足够简单适合中小型应用时间处理intlFlutter 生态最常用的时间格式化库鸿蒙构建Flutter 鸿蒙适配分支把 Dart 代码编译打包成鸿蒙应用可能有人会问为什么不直接上后端数据库洗衣房打卡的数据量很小本地存储完全够用。真到了需要跨设备同步做统计的那一天再加一个轻量后端也不迟。第一版要的是快速落地验证需求别给自己挖太深的坑。2. 环境准备与工程创建2.1 开发环境版本选择如果你已经装过 Flutter第一步先检查版本。我建议用最新稳定版因为鸿蒙适配分支和 Flutter 官方主干存在一定的版本绑定关系太老的版本很可能无法正常构建鸿蒙包。这里要特别强调标准的 Flutter SDK 目前不直接支持生成鸿蒙应用。需要安装鸿蒙适配分支的 Flutter SDK才能识别ohos平台。安装方式很简单从适配分支下载对应系统的 SDK 压缩包解压后配置到 PATH 环境变量里就行。命令行验证环境是否就绪我习惯先看版本再跑一遍诊断命令flutter --version flutter doctorflutter doctor会帮你检查 Dart SDK、Android 工具链、以及鸿蒙相关的工具链是否齐全。如果哪一项显示缺失照着提示补装即可。这个过程不建议跳步因为后面创建工程、打包构建时任何一个环境缺失都可能让你在错误信息里绕很久。2.2 创建 Flutter 工程环境没问题后在终端执行创建命令flutter create laundry_app这个命令会生成一个标准的 Flutter 项目骨架包含lib、android、ios等目录。创建完成后先用编辑器打开项目把默认的计数器示例代码删掉后续从零开始实现我们的业务。工程目录结构里lib目录是 Dart 代码的主战场。我习惯在lib下面再建几个子目录按职责划分models放数据模型pages放页面services放数据库操作逻辑state放 Provider 状态类。项目虽小目录结构一开始理清楚后面加功能不会乱。2.3 给工程添加鸿蒙平台支持标准的 Flutter 工程默认只有安卓和 iOS 平台目录鸿蒙属于新增平台需要额外执行命令补上flutter create --platformsohos .执行完成后项目根目录下会出现ohos目录里面存放鸿蒙应用工程的配置文件、源码入口、资源文件等。这一步做完项目才算真正意义上支持三大平台。我遇到过一位同学创建工程后直接跳过了这步一直用标准命令打包折腾了半天也没生成鸿蒙包最后才发现是缺了ohos平台目录。所以记住这句话跨平台支持不是天生的每个平台都要显式声明。3. 数据层设计3.1 字段设计洗衣房打卡应用的数据模型核心就两张表机器表和打卡记录表。机器表存的是静态信息机器编号、所在楼层、机器名称。机器当前状态虽然会变化但状态值可以实时计算不一定要存数据库。更合理的做法是机器表只存固定属性状态从最新的打卡记录推导出来。打卡记录表是核心表字段设计如下字段类型说明idINTEGER主键自增machine_idINTEGER洗衣机编号machine_nameTEXT洗衣机名称冗余存储方便历史记录展示start_timeTEXT开始打卡时间end_timeTEXT结束打卡时间statusINTEGER1 进行中2 已完成machine_name这个字段是刻意冗余的。如果只存machine_id查历史记录时还得关联机器表才能拿到名称。打卡这种低频操作多占一点点存储空间换取查询时的简单直接非常划算。3.2 用 sqflite 建库在pubspec.yaml里加两个依赖dependencies: flutter: sdk: flutter sqflite: ^2.3.0 path: ^1.8.0path库用来拼接数据库文件路径sqflite是对 SQLite 的 Flutter 封装。执行flutter pub get安装依赖后编写数据库帮助类核心代码框架如下class DBHelper { static final DBHelper instance DBHelper._(); DBHelper._(); Database? _database; FutureDatabase get database async { _database ?? await _initDB(); return _database!; } FutureDatabase _initDB() async { final dir await getDatabasesPath(); final path join(dir, laundry.db); return openDatabase( path, version: 1, onCreate: (db, version) async { await db.execute( CREATE TABLE machines( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, floor TEXT NOT NULL ) ); await db.execute( CREATE TABLE records( id INTEGER PRIMARY KEY AUTOINCREMENT, machine_id INTEGER NOT NULL, machine_name TEXT NOT NULL, start_time TEXT NOT NULL, end_time TEXT, status INTEGER DEFAULT 1 ) ); }, ); } }建表的 SQL 里status默认值是 1进行中。每次开始打卡插入一条新记录结束打卡时把对应记录的end_time写上去、status改成 2。这样历史记录天然就是一份完整的使用流水账。3.3 用 Provider 管理状态为什么不用 setState 硬扛洗衣房状态不是只在一个页面显示首页看板、历史记录、统计页面都要读取同一份数据。跨页面共享数据如果都用 setState那就要在每个页面各自查一遍数据库数据一变还得手动同步非常容易出不一致的 bug。Provider 解决的是这个核心问题让数据变成全局共享的响应式状态。谁修改了数据所有订阅了这个状态的页面自动刷新不需要手动通知。我习惯把 Provider 的状态类拆成两部分一部分是数据本身一部分是操作数据的方法。核心逻辑如下状态类继承 ChangeNotifier数据发生变化时调用notifyListeners()页面通过context.watch监听变化并重建。这种模式在小项目里写起来非常顺手比 Bloc 少了一堆样板代码比 GetX 又能少学一套魔法。4. 核心功能实现4.1 首页状态看板首页承担的是“一眼看到所有状态”的职责。打开应用后从数据库加载机器列表和进行中的打卡记录按状态分成空闲、使用中两类用卡片形式展示。洗衣机的状态推导逻辑放在状态类里核心思路是查询所有status 1进行中的打卡记录把这些记录对应的机器标记为使用中其余机器就是空闲。界面布局上外层用GridView展示机器卡片每张卡片显示机器名称、所在楼层和当前状态。状态用颜色区分使用中显示橙色进度条空闲显示绿色对勾一眼看过去非常直观。这里有个细节值得注意卡片点击后的行为随状态变化。使用中的机器点击后显示“结束打卡”按钮空闲机器点击后显示“开始打卡”按钮。这个逻辑放到 UI 层判断即可不要去改底层数据。4.2 打卡业务逻辑打卡是整个应用最核心的业务动作。开始打卡和结束打卡本质上都是对数据库的一轮更新操作。写这段逻辑时我特别推荐使用数据库事务。开始打卡涉及两步操作更新机器状态、插入打卡记录。如果不用事务中途任何一步失败都会导致数据不一致——机器显示使用中但没有打卡记录或者打卡记录存在但机器状态没变。开始打卡的核心代码Futurevoid startWash(int machineId, String machineName) async { final db await DBHelper.instance.database; await db.transaction((txn) async { await txn.insert(records, { machine_id: machineId, machine_name: machineName, start_time: DateTime.now().toIso8601String(), status: 1, }); }); notifyListeners(); }结束打卡的核心代码Futurevoid endWash(int recordId) async { final db await DBHelper.instance.database; await db.transaction((txn) async { await txn.update( records, { end_time: DateTime.now().toIso8601String(), status: 2, }, where: id ?, whereArgs: [recordId], ); }); notifyListeners(); }把数据库操作封装成事务这是我在实际项目中踩过不少坑后养成的习惯。洗衣房应用虽然数据量不大但状态一致性的问题一旦出现排查起来比写代码本身费事得多。4.3 历史记录与统计历史记录页要解决的问题有三个按日期分组展示打卡流水、显示每次洗衣的时长、统计当天总使用次数。按日分组用 SQL 的GROUP BY会很绕不如在 Dart 层处理查询时按start_time倒序取回所有记录然后在内存里按日期归组。数据量不大这样处理简单直接。统计单次洗衣时长就是用end_time减去start_time。需要处理一个边界情况还在进行中的记录没有end_time统计时直接跳过就行。个人统计页做得稍微丰富一点展示今天的洗衣次数、平均时长、最常用机器排行。这些数据都能用几条简单的 SQL 聚合查询搞定核心逻辑不复杂但用户的感知会好很多。5. 鸿蒙适配与打包发布5.1 鸿蒙工程目录与权限配置鸿蒙平台目录ohos的结构和安卓的android目录很像但又完全不一样。里面最核心的配置文件是module.json5相当于安卓的AndroidManifest.xml所有权限声明、页面注册都在这里做。洗衣房打卡第一版不需要联网如果以后要加扫码功能就得申请相机权限。申请权限的配置项加在requestPermissions数组里{ module: { requestPermissions: [ { name: ohos.permission.CAMERA }, { name: ohos.permission.INTERNET } ] } }权限只声明还不行运行时调用相机前还要在代码里做动态授权请求这个流程和安卓 6.0 之后的运行时权限机制非常像。5.2 构建 HAP 包Flutter 项目的构建命令在传统平台上分别是flutter build apk和flutter build ios。鸿蒙平台对应的命令是flutter build hap --release执行这个命令之前有一个关键步骤不能漏签名配置。鸿蒙应用的安装包 HAP 必须签名后才能安装到真机开发阶段可以使用自动签名发布阶段要换成正式签名证书。签名配置完成后构建产物会生成在build/ohos/release/目录下文件名通常是xxx-signed.hap。把这个文件发给测试手机安装应用就能跑了。5.3 真机调试要点真机调试和模拟器调试差别很大。鸿蒙模拟器在官方 IDE 里可以直接创建但真机才是真正能发现问题的地方。我把真机调试的关键步骤总结了一下首先开发者在鸿蒙手机上开启开发者模式打开 USB 调试用数据线连上电脑。然后用官方 IDE 的项目部署功能选择目标设备进行安装。最后查看运行日志时使用命令行工具而不是 IDE 自带的日志窗口过滤关键字时直接搜应用包名效率更高。我在下文中还会专门拆解一些常见坑这些都是实际调试过程中会遇到的问题。6. 常见问题与排查技巧实录6.1 编译错误专区错误现象可能原因解决思路flutter build hap找不到命令Flutter SDK 不是适配分支重新下载鸿蒙适配分支的 SDK构建时报ohos目录不存在创建工程时没添加鸿蒙平台执行flutter create --platformsohos .Gradle 依赖下载超时网络问题配置镜像源切换网络环境安装到真机提示签名错误签名证书未配置或过期重新检查签名配置这些编译错误有一个共同特点报错信息往往不会直接指向问题根源而是报一个上游的错误排查时根据现象反向推断。每次遇到编译问题我都习惯先做三件事检查 Flutter 版本、检查平台目录是否存在、检查签名配置。80% 的问题都能在这三步里找到答案。6.2 运行逻辑 bug 排查编译通过不代表功能正常运行时的逻辑 bug 才是耗时大户。我在调试时遇到过一个典型的幽灵状态 bug首页显示两台洗衣机使用中但历史记录里只有一条进行中记录。排查后发现根因在于我一开始没有使用事务插入打卡记录成功了但更新状态时抛了异常导致状态和数据对不上。从那以后所有涉及多表更新的操作我都强制要求自己用事务包裹。这也印证了一个道理排错的最好方法是写代码时就避免错误产生的条件。6.3 鸿蒙适配特有坑鸿蒙适配过程中有一些平台特有的问题普通 Flutter 项目里根本不会遇到。第一个坑是插件兼容性。Flutter 社区的大部分插件默认只支持安卓和 iOS要在鸿蒙平台上跑需要插件本身提供鸿蒙原生实现。我用的sqflite和path恰好都有鸿蒙支持但如果你引用了某个冷门插件很可能会在编译阶段直接报错。选依赖时提前检查鸿蒙兼容性能省掉后面大量的适配时间。第二个坑是界面安全区域。鸿蒙系统对状态栏高度、底部导航条的处理和安卓不完全一致如果不做安全区域适配页面底部的内容会被系统导航条遮挡。解决办法是在页面外层包一层安全区域组件。第三个坑是应用图标和名称。鸿蒙应用的图标要求和安卓不同直接复用安卓图标可能会被系统裁剪成奇怪的形状。发布前在ohos目录的资源配置文件里单独配置适配鸿蒙规范的图标。6.4 用户侧反馈的迷之操作应用上线测试阶段收到过一条让我哭笑不得的反馈有同学打卡后没点结束第二次来洗衣房时发现那台机器处于使用中状态自己没法再打卡。这其实是产品设计问题不是技术 bug。但用户不会管你怎么设计他们只关注自己的需求有没有被满足。我后来加了一个“强制结束”的容错机制使用中的记录如果超过两个小时视为超时记录可以用强制结束按钮重置。这种超出原始需求但真实存在的场景往往比需求文档里写的功能更影响用户体验。最后的话这个项目从需求到上线前后花了两周时间。我自己最大的感受是技术上真正难的地方不是代码本身而是把状态流转想清楚。打卡应用看着简单但“开始”和“结束”两个动作牵扯到状态判断、数据库更新、界面同步一环扣一环。好在这个项目用 Flutter 的跨平台能力加上 Provider 的响应式机制把复杂度控制在了合理的范围内。鸿蒙适配的部分因为整个生态还在快速演进看文档时一定要确认版本匹配。同一个适配分支可能在某个版本之后构建命令都变了这时候旧的教程反而会误导你。如果在实际开发中遇到文章里没覆盖到的问题建议先到鸿蒙适配项目的 issue 区翻一翻很多坑前人已经踩过并且给出了解决方案。
阅读完成 · 觉得有帮助?
咨询建站