
作为一个白天写业务代码、晚上回家还要伺候猫主子的开发者我一直想给自家那只一到饭点就喵喵叫的橘猫做个喂食管理工具。最初的想法很简单记录它什么时候吃了、吃了多少、下次该几点喂。但真正动手时发现选技术栈这件事本身就够折腾一轮。后来我把目光放在了 Flutter for OpenHarmony 上用一套 Flutter 代码把猫咪管家App跑在了 OpenHarmony 设备上其中“添加喂食”这个核心功能从数据建模到界面实现再到定时提醒完整走了一遍。这篇博文就把整个实战过程摊开来讲包括环境搭建、状态管理、存储方案、通知触发逻辑以及我在真机调试时踩过的几个印象深刻的坑希望能给同样在 Flutter 和 OpenHarmony 之间徘徊的同学一些参考。1. 几个关键决策为什么是“Flutter OpenHarmony 猫咪管家”1.1 跨平台框架那么多为什么偏偏选 Flutter做 OpenHarmony 应用市面上最主流的路线其实是直接用 DevEco Studio 加 ArkTS 写原生应用。这条路的问题在于如果你手头已经有一套 Flutter 代码或者团队成员更熟悉 Dart那迁移成本会非常高。我当时手里正好有一个之前在 Android 上写过的猫咪管家原型UI 和逻辑都在 Flutter 里与其在 ArkTS 里重新实现一遍不如直接调研 Flutter 在 OpenHarmony 上的适配情况。实测下来Flutter for OpenHarmony 的适配层已经能把大部分原生 Flutter 能力映射到 OpenHarmony 的 API 上基础的 Widget 渲染、手势系统、路由管理都能正常工作。对于猫咪管家这种以表单、列表、状态管理为主的工具类 AppFlutter 的跨端优势是实打实的我不用维护两套 UIArkTS 那边只需要关心 OpenHarmony 特有的系统能力接入比如后续要接摄像头识别猫咪进食行为时再通过原生通道去调 OpenHarmony 的相机接口就好。1.2 猫咪管家App的核心功能边界猫咪管家这个项目我规划了四个功能模块猫咪档案管理、喂食记录、定时提醒、健康数据统计。本文聚焦的是“添加喂食”这条完整链路因为它是整个App里最能体现“数据—状态—UI—系统能力”协同的部分。“添加喂食”看起来只是填个表单点保存实际上拆开之后包含这些子问题喂食记录的数据结构怎么设计、保存之后怎么通知列表刷新、喂食时间到了怎么触发提醒、App 被杀了之后提醒还能不能生效。这每一个子问题在 OpenHarmony 平台上都有一些跟 Android 不一样的细节后面我会逐个拆解。1.3 一次“添加喂食”操作背后的数据流全景先给出一张流程视角的图景用户在添加喂食页选择食物类型猫粮、罐头、零食、输入克数、设置喂食时间、填写备注点击保存后数据写入本地存储同时更新全局状态喂食记录列表立刻出现新条目。如果用户设置了“定时喂食”提醒则向系统注册一个提醒代理。整个过程涉及 UI 层、状态管理、存储层、系统提醒服务四个环节每一层我都做了单独的封装。2. 环境准备跑通 Flutter for OpenHarmony 的完整流程2.1 开发工具链的版本组合要在 OpenHarmony 上跑 Flutter先要明确一个概念你用的不是普通 Flutter SDK而是 OpenHarmony 适配版的 Flutter SDK。社区里一般叫它 flutter_flutter 的 OpenHarmony 分支或者直接叫 Flutter for OpenHarmony。我搭建环境时用的组合是DevEco Studio 4.0 以上版本 OpenHarmony SDK API 10 Flutter OpenHarmony 适配版 SDK。这里有一个容易被忽略的点DevEco Studio 自带的 SDK Manager 里需要额外安装“OpenHarmony”那一栏的系统库而不是 HarmonyOS 的因为两者的 API 差异会对 Flutter 适配层的编译产生影响。我当时最先踩到的坑就在这里OpenHarmony SDK 装得不全导致 Flutter 工程在编译原生部分时找不到ohos相关的 Gradle 插件依赖。这个问题不是你 Flutter 代码写错了而是底层 C 与原生桥接层的编译环境没准备好。2.2 Flutter 工程里如何加入 OpenHarmony 平台支持普通 Flutter 工程默认只有 android、ios 两个平台目录OpenHarmony 支持通常是以额外的平台目录形式集成进去的。具体做法是在项目根目录执行适配版 Flutter SDK 提供的命令生成ohos平台目录然后把这个目录作为一个原生工程导入 DevEco Studio。flutter create --platformsohos .如果你的适配版 SDK 不支持这个参数就需要手动创建一个空的ohos目录并配置build.gradle、ohos-package.json、module.json等文件。这一步我建议直接用官方模板手写容易遗漏module.json里的abilities声明一旦漏了安装到真机上会直接报“找不到入口 Ability”App 根本起不来。2.3 验证 Hello World 一定要用真机OpenHarmony 的模拟器体验一直一般尤其是涉及相机、通知这类系统能力时模拟器的行为跟真机差别很大。我建议从一开始就直接连真机调试。在 DevEco Studio 里配置好设备连接后依次检查设备是否开启 Developer Mode、hdcHarmonyOS Device Connector能否识别设备、Flutter 的ohos运行配置是否指向了正确的 entry module。跑通 Hello World 之后先别急着写业务代码。我习惯先验证两个基础能力Flutter 页面能正常渲染以及MethodChannel能成功调用 OpenHarmony 的原生接口。这两个通了后面加功能才不慌。我用一个最简单的通道验证Flutter 端调原生返回当前系统版本号能弹出来就说明桥接层没问题。3. 猫咪管家App的数据建模喂食记录不是简单存个时间3.1 FeedRecord 模型设计中的字段取舍“添加喂食”首先要有地方放数据。我定义了一个FeedRecord模型字段如下class FeedRecord { final String id; final String catId; final String foodType; // 猫粮 / 罐头 / 零食 final int amountGram; // 喂食克数 final DateTime feedTime; // 喂食时间 final String note; // 备注 final bool isScheduled; // 是否定时提醒 FeedRecord({ required this.id, required this.catId, required this.foodType, required this.amountGram, required this.feedTime, this.note , this.isScheduled false, }); }字段设计时有几个细节值得说一下。id我用的是时间戳加随机数拼接保证离线状态下也能生成唯一主键amountGram用int而不是double因为日常喂食克数基本是整数用整数还能减少后续统计时的浮点误差isScheduled是给列表页用的用来区分“这条记录当时设了提醒”后续用户取消提醒时也要同步改这个字段。catId是为了将来多猫家庭扩展虽然我这个项目目前只有一只橘猫但多猫场景在真实用户里很常见。3.2 存储层OpenHarmony 上选 Preferences 还是数据库喂食记录的数据量不会特别大一天几十条顶天了所以存储方案我没有一上来就上大型数据库。OpenHarmony 本身提供了一套轻量级偏好存储类似 Android 的 SharedPreferences适合存简单的键值对。但如果只存 JSON 字符串每次添加喂食都要把整个记录列表读出来、改完再写回去数据量到几百条之后性能会明显下降。我的做法是分两层第一层用偏好存储保存“当前选中猫咪”这类轻量配置第二层用 OpenHarmony 的关系型数据库接口存喂食记录。关系型数据库虽然要多写不少建表和数据访问代码但胜在支持条件查询后续做“按日期统计喂食总量”这种需求时一条 SQL 就能搞定不用把数据全部读出来在内存里过滤。建表语句大致是这样CREATE TABLE IF NOT EXISTS feed_record ( id TEXT PRIMARY KEY, cat_id TEXT NOT NULL, food_type TEXT NOT NULL, amount_gram INTEGER NOT NULL, feed_time INTEGER NOT NULL, note TEXT, is_scheduled INTEGER DEFAULT 0 )注意feed_time我存的是毫秒级时间戳而不是日期字符串。原因很简单时间戳在排序、范围查询、时区换算上都是最优解显示层再转成YYYY-MM-DD HH:mm完全来得及。3.3 用 Provider 搭起全局状态让列表页自动感知新记录存储层负责持久化但页面之间要能实时联动还缺一层内存状态管理。这里我用的是 Flutter 社区最常用的 Provider。选它而不是 Bloc 或者 Riverpod主要考虑项目规模猫咪管家这种中型 App 用 Provider 已经足够团队成员上手成本也低不至于为几十个页面引入一套过于复杂的状态机。我的具体分工是这样的FeedRepository负责跟数据库打交道FeedListModel是一个继承ChangeNotifier的类内部持有当前喂食记录列表和加载状态然后通过ChangeNotifierProvider挂在 Widget 树顶层。添加喂食页保存成功后只需要调用FeedListModel.addRecord()列表页里用Consumer监听的组件就会自动刷新。这个设计解决了“页面A添加数据页面B要自动更新”的典型组件通信问题。后续如果你要加“喂食后自动弹出下一次建议时间”之类的功能也只需要在FeedListModel里加一个计算属性而不需要动 UI。4. 添加喂食的完整实现从表单到通知触发4.1 添加喂食页面的交互拆解页面交互我按“所见即所得”的原则来设计顶部是一个大的食物类型选择区用三个卡片横向排列猫粮、罐头、零食选中状态通过颜色和图标区分中间是克数输入框配置了数字键盘下面是一个时间选择器和一个备注输入框底部是大的保存按钮。UI 上有一个容易忽略的细节克数输入框的校验逻辑我要求必须是 1 到 500 之间的整数小于 1 或大于 500 都直接弹提示。因为猫咪一次进食量如果超过 500 克大概率是用户输入错误与其让脏数据进库不如在表单层拦截。时间选择器我默认给当前时间因为大部分用户是“喂完了记一笔”的场景少部分才是提前设置定时提醒默认当前时间能减少一步操作。4.2 表单提交的逻辑链路和两条关键校验保存按钮的onPressed绑定的是_submitForm方法。它的核心逻辑如下Futurevoid _submitForm() async { if (_selectedFoodType null) { _showToast(请选择食物类型); return; } if (_amountController.text.isEmpty) { _showToast(请输入喂食克数); return; } final amount int.tryParse(_amountController.text); if (amount null || amount 0 || amount 500) { _showToast(喂食克数必须在1到500之间); return; } final record FeedRecord( id: ${DateTime.now().millisecondsSinceEpoch}_${Random().nextInt(9999)}, catId: _currentCatId, foodType: _selectedFoodType!, amountGram: amount, feedTime: _selectedTime, note: _noteController.text.trim(), isScheduled: _isScheduleEnabled, ); await _feedListModel.addRecord(record); if (record.isScheduled) { _reminderHelper.registerFeedReminder(record); } Navigator.of(context).pop(true); }这段代码里有两条关键的校验第一克数必须先tryParse再判断范围只判断非空是不够的用户可能输入字母或者负数第二保存成功后才去注册提醒如果数据落库失败不应该产生一条“幽灵提醒”。这也是我在实际开发中常见的顺序问题很多人先注册提醒再存数据库结果数据库写入失败提醒却已经挂在系统里了到点就会弹一条不存在的喂食提醒体验非常糟糕。4.3 喂食记录列表与下拉刷新的联动添加完记录返回列表页列表要能立刻看到新数据。这里我用ConsumerFeedListModel包裹列表组件只要FeedListModel的notifyListeners()被触发列表就会自动 rebuild。列表项上会显示食物类型图标、克数、时间以及一个“已提醒”的小标签。列表页另外加了RefreshIndicator做下拉刷新这个不只是做做样子。因为数据存储层跟内存状态之间没有做自动同步极端情况下比如多设备登录或者数据被系统清理内存里的记录可能和数据库不一致。下拉刷新会重新从数据库加载一次数据保证看到的一定是最新的。RefreshIndicator( onRefresh: () _feedListModel.reloadFromDatabase(), child: ListView.builder(...), )这里有个细节RefreshIndicator在列表内容不满一屏时下拉手势很难触发。解决方式是在ListView上设置AlwaysScrollableScrollPhysics否则列表项只有五六个的时候下拉刷新会非常难用你以为是功能坏了其实是滚动物理特性默认值在作怪。4.4 定时提醒Timer方案与系统提醒代理方案怎么取舍“添加喂食”里最有系统集成感的一步是定时提醒。我在项目里同时实现了两种方案方案一是纯 Flutter 侧 Timer 加本地通知。用户在 App 内时用一个Timer延时到目标时间弹一个 Flutter 内部的弹窗或通知。优点是纯 Dart 实现完全跨端不需要理解 OpenHarmony 的 API缺点是 App 一旦被系统清理或者进程被杀Timer 就失效了提醒永远不会触发。方案二是走 OpenHarmony 的提醒代理能力也就是ReminderAgentManager。它把提醒任务注册到系统侧由系统统一调度App 进程不在也能到点触发。区别就像你自己定闹钟和请别人到点打电话叫你前者手机没电就没戏后者即使你关机对方到点还是知道该提醒。我的最终做法是两者结合App 在前台时用方案一体验更即时同时把提醒通过MethodChannel注册到 OpenHarmony 系统侧保证后台被杀之后依然能触发。注册的时机就是上面_submitForm里的_reminderHelper.registerFeedReminder(record)。方案进程被杀后是否生效实现复杂度适用场景Flutter Timer 本地通知否低App 前台使用的兜底提醒OpenHarmony 提醒代理是中用户关闭 App 后的准点提醒注册系统提醒有一个新增弹窗权限的坑OpenHarmony 对通知权限管理比较严格第一次调用相关接口时需要动态申请ohos.permission.PUBLISH_AGENT_REMINDER权限如果用户拒绝了后续提醒注册会静默失败不会抛异常。这一点一定要在 UI 上给用户明确的反馈否则用户会以为设置了提醒但永远等不到通知。5. 实战中绕不开的坑我真机跑起来的四次报错5.1 Gradle 插件配置的经典报错真机调试第一个遇到的坑就是这个报错信息you are applying flutters main gradle plugin imperatively using the apply s...。原因很直接Flutter 在 OpenHarmony 平台上的 Gradle 集成方式和 Android 不完全一样适配版 Flutter 要求以插件方式声明式地应用 Gradle 插件而旧模板里还是用apply命令式写法。修复方式是把外层build.gradle里的apply换成plugins块plugins { id com.flutter.gradle version 1.0.0 }如果你的项目里还有第三方插件也用同样的命令式写法需要统一改成声明式否则会出现“插件被重复应用”之类的连锁报错。5.2 Impeller 渲染引擎在 OpenHarmony 上的兼容性问题Flutter 3.10 之后默认启用 Impeller 渲染引擎但在 OpenHarmony 适配版上Impeller 的支持并不完整我在跑列表滚动时遇到了明显的渲染毛刺和偶发闪屏。排查下来发现是 Impeller 在 OpenHarmony 的 GPU 驱动上做某些着色器编译时会出错官方推荐暂时关闭 Impeller回到 Skia 渲染。关闭方式是在 Flutter 启动参数里加--no-enable-impeller或者直接在 AndroidManifest / module.json 里配置对应的 Flutter 引擎开关。这也是为什么很多 OpenHarmony 上的 Flutter 应用跑起来总感觉跟 Android 上“画风不太一样”其实就是渲染引擎不同导致的。这个问题不算致命但如果你发现列表动画或者页面转场特效异常第一反应应该是检查渲染引擎而不是怀疑自己代码写错了。5.3 Provider 在使用状态管理时容易“丢状态”有段时间我遇到一个诡异问题添加喂食完成后返回列表页数据偶尔会消失再次进入才恢复。排查后确认是路由跳转时的问题。我当时用了Navigator.push跳转到添加页在添加页内部通过Provider.ofFeedListModel(context)拿到的是同一个实例理论上没问题。但添加页在键盘弹起、页面重建等场景下如果 Provider 的create方法写得对状态不会丢。真正让我丢状态的原因是我在列表页的didChangeDependencies里做了多余的reloadFromDatabase和Consumer的刷新逻辑互相竞争导致界面先显示空数据异步加载完成后再恢复正常。解决办法是把数据加载统一收敛到FeedListModel的初始化方法里页面层只负责“监听变化”和“触发用户操作”不要每个页面各自为政去加载数据。这个教训的核心是状态管理工具只是帮你共享数据数据加载的职责边界还是要你自己定清楚。5.4 Flutter AAR 集成方式与纯 Flutter 工程怎么选OpenHarmony 上接入 Flutter 还有一种方式是 Flutter AAR即把 Flutter 引擎打包成 AAR 库由原生 ArkTS 工程引入。这个方案适合你已经有一个比较完整的 OpenHarmony 原生应用只是想把某个 Flutter 页面嵌进去而不是整个应用都用 Flutter 写。猫咪管家一开始我也考虑过这种方式因为想着后面可能要用原生摄像头做猫咪识别但实际评估后发现纯 Flutter 工程配合 MethodChannel 就能解决原生能力调用问题没必要维护一个更复杂的混合工程结构。AAR 方式的另一个问题是调试体验差每次改 Dart 代码都要重新打 AAR、重新编译原生工程迭代速度明显比纯 Flutter 工程慢。如果你不是有强制的原生集成需求我建议先跑通纯 Flutter 工程原生能力通过方法通道逐个扩展。5.5 新建项目跑不起来的网络与缓存问题最后说一个新手几乎必踩的坑Flutter 新建项目后第一次运行卡在 Gradle 下载依赖一动不动甚至直接报超时。OpenHarmony 的 Gradle 插件和依赖大多从公网仓库下载部分地区网络状况不好的时候非常痛苦。我的经验是先配置镜像仓库在build.gradle里把仓库地址替换为可用的镜像同时把 Gradle Wrapper 的版本固定到适配版 SDK 验证过的版本不要用最新的 Gradle因为最新版和旧版插件经常不兼容。另外一个额外提示DevEco Studio 的构建缓存有时候会把旧的编译产物混进来导致你改了 DART 代码但真机上还是旧版本。遇到这种情况时不用慌清理掉ohos目录下的build和.gradle缓存目录重新构建一般就能解决。6. 写在最后猫咪管家后续可以这样扩展做完“添加喂食”这条链路之后整个 App 的骨架已经立住了。我给自己的扩展计划是两个方向一是接入 OpenHarmony 的 HDI 能力通过硬件接口读取智能猫碗的重量传感器数据让“吃了多少”从手动输入变成自动感知这才是喂食记录该有的样子二是接入摄像头识别猫咪的进食行为记录每天的进餐时长和频次结合喂食数据做简单的健康趋势图。这两个方向都会涉及更多原生的东西但好消息是基础架构已经有了数据层、状态层、UI 层已经解耦到时候最多只是再加一个方法通道和几个原生插件的事情。最后分享一个我个人的小体会跨平台开发最怕的不是写功能而是环境问题。Flutter for OpenHarmony 的坑很大一部分集中在环境搭建和渲染引擎上真正写业务逻辑的时候反而很顺畅。如果你也打算在 OpenHarmony 上做 Flutter 应用建议把版本组合、构建缓存、渲染引擎这些基础项先固定好再开始写业务代码。反正我建这个猫咪管家项目踩完一轮坑之后现在再让我在 OpenHarmony 上从零搭一个 Flutter 工程半小时之内能搞定希望你看完这篇也能少走些弯路。