这两个月我一直在折腾一个用 Flutter 写的 OpenHarmony 家庭药箱管理 App,中间还加了血压记录模块。起因很简单:家里老人常备药有好几盒,有的快过期了也没人知道;血压记录本写得密密麻麻,但要看出趋势变化却难得很。于是我想着与其等着哪天吃错药或者血压波动没被注意到,不如直接自己做一个工具来管这件事。选 Flutter 是因为我本来就更熟 Dart 生态,而 OpenHarmony 这边正好也需要一个跨端快速落地的方案,两个一凑,项目就这么开始了。
这篇文章我不打算讲太多虚的,直接把整个项目的需求拆分、技术选型、核心实现、踩坑过程都拆开给你看。内容涉及药品库存管理、过期提醒、用药定时、血压记录与趋势图表,以及 Flutter 在 OpenHarmony 上最常见的那些坑——组件通信、异步机制、平台通道、渲染引擎兼容性等等。如果你正打算用 Flutter 做 OpenHarmony 应用,或者手头刚好有类似的家庭健康类工具需求,这篇文章里的工程方案和代码逻辑可以直接抄作业,能帮你少走不少弯路。
1. 项目背景与整体方案设计
1.1 为什么选 Flutter + OpenHarmony
先说说技术选型的来龙去脉。OpenHarmony 是当前国内关注度很高的开源操作系统,应用生态正在快速建设,但相比 Android 和 iOS,它的原生应用开发门槛还是偏高,尤其是如果你的目标用户有多个设备、多个系统版本,纯原生开发的人力成本会一直上涨。而 Flutter 的优势在于一套代码多端跑,UI 层完全自绘,渲染不依赖系统原生控件,这个特性放到 OpenHarmony 上其实非常关键,因为 OpenHarmony 的控件体系还在不断演进,自绘引擎反而能帮我们规避一部分原生控件不一致的坑。
当然,选择 Flutter 也不只是因为 UI 层。我在这个项目里要处理的是家庭健康场景,逻辑复杂度集中在业务层和数据层,比如药品到期预警、血压趋势统计、用药提醒调度,这些跟平台耦合度很低,用 Dart 来写没有任何问题。再加上 Flutter 生态里已经有很多成熟的库,下拉刷新、图表绘制、本地数据库、日期选择器这些东西都有现成的轮子,比在 OpenHarmony 上从零写原生页面轻松太多。
不过必须说清楚,目前 Flutter 对 OpenHarmony 的支持并不是官方默认就集成好的,需要借助 OpenHarmony 社区维护的 Flutter 分支来构建引擎和运行环境。社区方案已经能跑通大部分场景,但像 native 插件、平台视图、相机调用这些还是会遇到兼容性问题。这个项目我是在功能选型上有意识地避开了过度依赖原生能力的部分,把核心逻辑放在 Flutter 侧,原生侧只保留通知和少量交互能力,这也是后面项目整体进度没有卡死的重要原因之一。
1.2 家庭药箱 + 血压记录的功能规划
家庭药箱管理这个需求,乍一看很简单,但真正拆开之后就发现细节不少。我这里按照家里人实际使用的场景来规划功能,最后收敛成四块核心能力。
第一块是药品信息管理,包括药品名称、分类、规格、剂量、剩余库存、存放位置、生产批号和有效期。药品分类我分成了常备药、处方药、外用药、儿童药和保健品几类,方便后续做分类过滤和用药提醒的差异化处理。库存字段要区分整盒数和拆零余量,不然就会出现“盒子里还剩两片但系统显示库存1盒”这种和实际脱节的情况。
第二块是有效期和库存预警。药品过期是个刚需,我默认提前 60 天开始提示,提前 7 天加强提醒,过期药会在列表顶部高亮拦截。同时库存低于设定阈值时也要提示补药,不然提示过期却有药、提示补药却发现家里还有一抽屉,就会很尴尬。
第三块是用药提醒。家里人每天要吃的药种类不多,但时间点很固定,所以我没有做特别复杂的时间表引擎,就是简单的“早/中/晚/睡前”四个时段配置,配合每日重复的提醒计划。这一块后期牵涉到重启后提醒恢复的问题,后面讲实操的时候会单独说。
第四块是血压记录。记录包含收缩压、舒张压、心率、测量时间和备注信息,额外支持按成员维度分开记录。数据展示方面我用折线图展示最近 30 天或者自定义时间段的收缩压和舒张压趋势,并标出正常范围参考线。血压记录和用药提醒之间没有做联动,因为目前医学上建议个性化的血压管理和用药调整要有医生参与,作为一个家庭工具,我把重点放到了记录和可视化上,避免给出不专业的建议。
1.3 技术选型与架构拆分
技术栈方面,Flutter 版本我用的社区 OpenHarmony 分支,Dart 相关状态管理选择了 Provider,没有一上来就上 Bloc 或者 Riverpod。家用工具类 App 的页面复杂度不算高,Provider 的写法足够直白,团队成员接手也容易,不必要为了架构炫技增加理解成本。数据库用的是 sqflite 的 OpenHarmony 兼容实现,虽然 OpenHarmony 官方有分布式数据管理服务,但考虑到单机场景为主、多设备协同是加分项而不是必选项,我暂时先用本地 SQLite,等后续需要支持多设备同步再切分布式数据库也不迟。
图标库选择了社区常见的 Material 风格图标,绘图部分用了 fl_chart,这个库的折线图性能表现不错,自定义程度也够,血压趋势图需要的参考线、点击高亮这些功能基本都覆盖了。页面跳转用系统内置的 Navigator 2.0 简版,没有引入额外的路由框架,因为页面总量八个左右,路由管理自己用 Map 维护反而更可控。
后端我直接放弃了。家庭场景不需要账号系统,数据全部存在本地,后续如果需要备份或者家庭成员间共享数据,再考虑接云同步。这个决定让整个项目的网络权限为零,也趁机躲开了大量安全和合规的坑,而且对 OpenHarmony 应用的审核来说,权限越少越有利。
架构上我分成了三层:UI 层负责页面展示和交互,业务层放药品管理、血压记录、提醒调度这些核心逻辑,数据层负责本地数据库读写。组件之间通过 Provider + 消息通知来做状态同步,底层数据变更统一走数据层接口,避免出现多个页面各自维护一份数据副本的经典混乱场景。
2. 核心功能预测与实际拆解
2.1 药品管理:从录入到过期提醒
药品信息的录入表单是整个项目最普通的开发工作,但是数据模型设计直接影响后面所有功能的复杂度。药品字段我最终定成下面这组。
class Medicine { final int id; final String name; final String categoryKey; // otc / rx / external / child / supplement final String spec; // 规格,比如 0.25g*24片 final String dose; // 用法用量描述 final int stockBox; // 整盒数量 final int stockPill; // 拆零余量 final DateTime? expiresAt; // 有效期截止日 final String location; // 存放位置,比如"客厅药柜第二层" final String? remark; }为什么要把拿药逻辑封装到业务层?因为库存操作不只有“录入”这一种场景,还有“吃药消耗”。每次记录用药后,系统默认自动扣除库存,扣除顺序是先扣拆零余量,余量不够再扣一整盒。这个逻辑如果散落在各页面里,后面一定会出现数据对不上的问题,我在第一版就是这样,结果三天后药品库存就变成负数了。
过期提醒我用的是一个定时器加列表扫描的方案。每天打开 App 或者切换前台时扫描一次数据库,把 60 天内到期、7 天内到期、已过期三个档位的药品数量统计出来,放到首页顶部的提醒卡片里。同时用药提醒的每日计划里会额外插入一条“检查过期药”的通知任务,双重保险确保不会漏看。
值得提醒的是,有效期的录入一定要设计成支持“盒子上只写了年份月份”的情况,所以我在日期选择上有两个模式:精确到日和仅精确到月。精确到日时直接用年月日;仅精确到月时默认取当月最后一天作为过期日,这样在计算剩余天数时会更保守,对健康场景来说宁可提前提醒也不能压线。
2.2 血压记录:从表单到趋势图
血压记录是这个项目里第二个核心模块,也是用户体验最容易做砸的部分。测量血压本身流程就多,用户很可能是睡眼惺忪坐在血压计旁边打开 App,如果录入表单复杂到要输入五次才能完成,那基本就弃用了。
所以我把血压录入设计成了一个尽量傻瓜化的表单:收缩压、舒张压、心率三个数字输入框,默认带上一个适合大多数人的基础判断,即根据基本常识判断记录是否在合理范围内,超出正常值范围时用颜色给出轻度提示,但不打断输入流程。测量时间默认取当前时刻,但也支持手动修改,方便用户补录之前漏记的数据。
血压数据模型大概是这样的:
class BloodPressureRecord { final int id; final String memberId; final int systolic; // 收缩压 mmHg final int diastolic; // 舒张压 mmHg final int pulse; // 心率 bpm final DateTime measuredAt; final String? note; }趋势图部分我用了 fl_chart 的 LineChart。横轴是时间,纵轴是血压值,分别画两条折线代表收缩压和舒张压,同时画了一条浅灰色的参考区域代表“正常血压”分区。这里有一个细节要提:参考线的位置不是随政策走的,而是在代码里用常量维护,方便未来调整。血压数据量较少时,折线图会显得很稀疏,我做了个优化,当时间跨度较大时自动切换为柱状平均值的显示方式,按周聚合数据,这样图表看起来更平滑。
还有一个很容易被忽略的问题:不同家庭成员的血压数据务必分开存储。哪怕目前你只管家里一个人,模型上也应该预留 memberId 字段。我见过不少一开始没留成员维度的小工具,后来加多用户支持时等于重构了一遍,那个成本比一开始就加字段高出好几倍。
2.3 组件通信与状态管理
组件通信是 Flutter 开发中永远绕不开的话题,也是这个项目里一开始让我踩了不少坑的地方。Flutter 的组件通信大体分三类:父子组件之间用构造参数和回调、跨层级祖先组件用 InheritedWidget 或者 Provider、完全解耦的模块之间用事件总线或者 Stream。在这个家庭药箱 App 里,我大部分通信是跨页面的,所以直接选择了 Provider。
为什么不用 InheritedWidget 硬写?因为 Context 的嵌套一多,读数据时容易一不留神拿到错误的父级实例。Provider 本质上是对 InheritedWidget 的封装,解决了泛型数据读取的问题,同时配合 ChangeNotifier 很容易实现页面局部刷新。具体到项目里,我用三个顶层 Provider 来拆解业务:MedicationProvider 负责药品的增删改查和库存变动,BloodPressureProvider 负责血压记录的新增和查询,ReminderProvider 负责用药提醒的启停和状态同步。
还有一个值得单独讲的通信场景是首页和子页面之间的联动。首页要显示库存和血压摘要,点击摘要卡片又会跳转到详情页,而详情页里的修改操作要反向刷新首页数据。我的做法是在 Provider 里统一维护一个版本号字段,增删改操作都走数据层的方法,并在方法内部调用 notifyListeners。首页通过监听这个 Provider 的 version 变化来自动刷新摘要数据,避免靠手动回传参数这种脆弱写法。
至于那种更解耦的模块间通信,比如吃药提醒触发后要同时更新库存和统计记录,我用了 Flutter 的 StreamController 做了一个事件总线。提醒触发 -> 库存扣除 -> 通知刷新这一条链路在两个 Provider 之间来回拉扯有点丑,用总线发一个“用药完成”事件,让两个 Provider 各自响应,反而清爽很多。
3. 实操过程与关键代码复现
3.1 OpenHarmony 环境搭建与工程初始化
环境搭建是真的劝退了不少人。OpenHarmony 上跑 Flutter 目前没有一键式集成方案,走的是一个“OpenHarmony 主工程 + Flutter module”的混合工程模式。我当时的步骤如下,每一步都踩出来了,按这个顺序大概率一次能通。
第一步,准备 OpenHarmony SDK 和 DevEco Studio。下载 OpenHarmony SDK 后,在 DevEco Studio 里配置好 SDK 路径,并新建一个 empty ability 的工程作为宿主应用。这一步注意 OpenHarmony SDK 的版本要和你的设备系统版本匹配,不匹配的话后面签名和安装都会出问题。
第二步,拉取 Flutter 的 OpenHarmony 分支。社区维护的仓库地址是 flutter_flutter 的 openharmony 版本,我用的稳定分支。配置好环境变量后,用这个分支的 flutter 命令创建 module。
git clone https://gitee.com/openharmony-sig/flutter_flutter.git cd flutter_flutter git checkout 3.7.12-ohos # 也可以选其他版本,注意和文档保持一致 export PATH=$PWD/bin:$PATH flutter --version第三步,创建 Flutter module。OpenHarmony 的 Flutter 集成方式和 Android 很像,先 flutter create 生成一个 module 工程,然后在 DevEco Studio 的宿主工程里添加依赖关系,让宿主工程能够引用 Flutter module 生成的产物。
flutter create --template=module --platforms=ohos medicine_box_flutter这里有个坑:--platforms 参数要写 ohos,如果不写,默认生成的工程是不含 OpenHarmony 平台目录的,后面集成时就会找不到 Flutter 引擎包。
第四步,在宿主工程的 build 配置里引入 Flutter module 的产物路径,然后进行首次编译。编译过程中会下载 OpenHarmony 平台相关的依赖,网络状况不佳的时候很容易卡在 Gradle 或者 hvigor 这一步,建议第一次编译时找个网络稳定的环境一口气跑完。
工程初始化这一个环节我前前后后折腾了两天左右,大部分时间都耗在版本匹配上。如果你也遇到“新建项目后跑不起来”的问题,大概率就是 SDK 版本、Flutter 分支版本、DevEco Studio 版本这三者没有对齐。我的建议是先确认 OpenHarmony 设备系统版本,再反向去查该版本推荐使用的 Flutter 分支,不要一上来就拉最新主干。
3.2 数据库设计与数据层实现
数据库我沿用 sqflite 的封装思路,但在 OpenHarmony 上要注意插件版本。社区已经发布过兼容 OpenHarmony 的 sqflite 实现,安装时指定对应版本即可,否则会在运行时报 MissingPluginException。
表结构设计上,我实际建了三张表:medicine、blood_pressure、reminder_plan。medicine 表对应药品模型,关键是给 expiry_date 和 stock 字段建好索引,因为过期提醒和库存查询都是高频操作。blood_pressure 表按 member_id 和时间字段建联合索引。reminder_plan 表记录每个成员的药品提醒计划,包含时段、药品 id、是否启用等字段。
class DatabaseHelper { static final DatabaseHelper _instance = DatabaseHelper._(); DatabaseHelper._(); Future<Database> get database async { return openDatabase( 'medicine_box.db', version: 1, onCreate: (db, version) async { await db.execute(''' CREATE TABLE medicine ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, category_key TEXT, spec TEXT, dose TEXT, stock_box INTEGER DEFAULT 0, stock_pill INTEGER DEFAULT 0, expiry_date TEXT, location TEXT, remark TEXT ) '''); await db.execute(''' CREATE TABLE blood_pressure ( id INTEGER PRIMARY KEY AUTOINCREMENT, member_id TEXT NOT NULL, systolic INTEGER NOT NULL, diastolic INTEGER NOT NULL, pulse INTEGER, measured_at TEXT NOT NULL, note TEXT ) '''); }, ); } }实现单例模式的数据库帮助类,所有数据访问都走这一层,避免多页面各自开着数据库连接,最后文件锁冲突。实际开发中我确实发现过,在 OpenHarmony 上 sqflite 对并发连接处理得不太稳,串行复用单连接是最可靠的做法。
库存扣除这个操作跨了药品和提醒两张表,我用了一个事务来保证一致性,顺序是先插入或用药记录,再扣减库存,最后更新提醒计划状态。如果中途异常,回滚后提醒不会重复触发,库存也不会被重复扣减。
3.3 提醒通知与平台通道
用药提醒是药箱 App 差异化的核心痛点。App 在前台的时候,用 Flutter 内部的 Timer 就能实现倒计时提醒;但切到后台,尤其是进程被杀之后,Timer 就失效了,必须依赖 OpenHarmony 侧的计划任务能力把提醒“托管”给系统。这就涉及 Flutter 和 OpenHarmony 之间的平台通道。
我在 Flutter 侧定义了一个 MethodChannel,让 Dart 调用原生侧的能力注册定时提醒。具体做法是在原生 Ability 里用 OpenHarmony 的 reminderAgentManager 创建定时提醒,传过去提醒内容、触发时间、重复规则这些参数。
class ReminderBridge { static const MethodChannel _channel = MethodChannel('medicine_box/reminder'); static Future<bool> scheduleReminder({ required int reminderId, required String title, required String content, required DateTime triggerTime, }) async { try { return await _channel.invokeMethod('scheduleReminder', { 'id': reminderId, 'title': title, 'content': content, 'triggerTime': triggerTime.millisecondsSinceEpoch, }); } on PlatformException catch (e) { debugPrint('schedule reminder failed: ${e.message}'); return false; } } }平台通道是 Flutter 和 OpenHarmony 之间的桥,但它在 UI 线程上执行,所以像这种需要注册系统服务的调用量不大还行,一旦涉及耗时的原生操作就一定要在原生侧起子线程,不能在主线程里阻塞。这个我在 OpenHarmony 侧是用 AsyncCall 机制来处理的。
另外,Flutter 的 PlatformView 在 OpenHarmony 上也是可用的,但兼容性和性能都还在完善中。我在项目里原本想用 PlatformView 嵌一个原生相机扫药品条码,后来实测发现渲染性能不理想,而且生命周期回调在部分系统版本上会出现错乱,最终我放弃了原生相机方案,改用 Flutter 侧做一个简易的手动条码输入入口。如果你是做更复杂的混合界面,建议提前在目标设备上做 PlatformView 压测,不要等集成完才发现卡顿严重。
3.4 页面实现与血压图表代码
页面部分我挑两个最有代表性的讲:一个是首页的药箱盘点页,另一个是血压趋势页。
药箱首页的核心是一个药品列表,但跟普通列表不同,它要处理库存状态和过期状态的视觉区分。我用 Flutter 的 RefreshIndicator 做下拉刷新,配合自定义的 ListView 实现按过期状态分组。每组开头显示一个摘要统计条,比如“已过期 2 种”、“30 天内到期 3 种”、“库存充足 12 种”。
过期判断写到数据层,避免在前端到处写日期比较逻辑。
ExpiryStatus getExpiryStatus(Medicine medicine) { if (medicine.expiresAt == null) return ExpiryStatus.none; final days = medicine.expiresAt!.difference(DateTime.now()).inDays; if (days < 0) return ExpiryStatus.expired; if (days <= 7) return ExpiryStatus.soon; if (days <= 30) return ExpiryStatus.warning; return ExpiryStatus.normal; }血压趋势页的图表我用 fl_chart 的 LineChart 来实现。这里有一个放在实际代码里的关键点:折线图数据量比较小的时候不要把每条记录直接变成一个点,否则图会像锯齿一样扎眼睛。我选择在数据层做时间窗口聚合,比如按天取平均值,然后传给图表组件。以下是核心配置片段:
LineChartData buildChartData(List<BloodPressureRecord> records) { final spots = records .map((r) => FlSpot( r.measuredAt.millisecondsSinceEpoch.toDouble(), r.systolic.toDouble(), )) .toList(); return LineChartData( minX: spots.first.x, maxX: spots.last.x, minY: 60, maxY: 180, lineBarsData: [ LineChartBarData( spots: spots, color: Color(0xFFE53935), barWidth: 2, isCurved: true, belowBarData: BarAreaData(show: true, color: Color(0x20E53935)), ), ], titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 32), ), bottomTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 32), ), ), ); }看这段代码你会发现我没有直接把 List 塞进 FlSpot,中间多了一层数据转换。这个转换层解决了一件事:血压记录测量时可能因为运动或不规范操作产生明显异常值,如果不加过滤直接画出来,趋势就会被个别极端数据带偏。我在转换层里加了中位数滤波,把偏离周围记录 50 mmHg 以上的点剔除或修正。这个过滤逻辑放在 UI 之前,也方便加单元测试。
3.5 渲染引擎与性能调优
Flutter 在这个项目里用到的新渲染引擎也值得一提。Impeller 是 Flutter 为了替代 Skia 而推的渲染引擎,主要解决 Skia 在跨平台渲染时的 shader 编译卡顿问题。在 OpenHarmony 上跑的那些 Flutter 版本,目前大多还是 Skia 为主,Impeller 的支持要看具体的社区分支状态。
我实测下来,在中等配置的 OpenHarmony 设备上,列表页滚动和图表页面渲染还算是跟手的,但如果你一屏铺满大量复杂组件,比如同时显示动画、阴影和半透明遮罩,帧率会明显掉下来。我的优化手段有三个:列表项尽量使用 const 构造、图片开启缓存并统一压缩尺寸、图表在页面不可见时暂停重绘。最后一个尤其重要,血压趋势页如果你在 PageView 里左右滑动,fl_chart 是会继续重绘的,在不可见页面上这是纯浪费,我通过对 PageView 的页面生命周期监听来暂停图表重绘,效果立竿见影。
4. 常见问题与排查经验
4.1 运行时未处理异常怎么定位
运行阶段印象最深的报错是 Flutter 在 OpenHarmony 上的未处理异常日志,长这样:
E/flutter (31173): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(error, ...)这个报错最让人难受的地方在于,它只告诉你有一个未处理异常,但崩溃 stack 往往不完整,甚至不指向你的实际业务代码。要定位这类问题,我的习惯是打开 Flutter 的 debug 模式,同时开启系统日志过滤,把 Dart 层和原生层的日志同时看。
这里有一个非常实用的做法:在 Dart 顶层用 Zone 捕获所有未处理异常,然后统一记录并上报到自己的日志系统。
void main() { runZonedGuarded(() { runApp(const MedicineBoxApp()); }, (error, stackTrace) { debugPrint('unhandled error: $error'); debugPrint('stackTrace: $stackTrace'); // 持久化到本地日志表,方便App内查看 }); }项目里遇到的未处理异常,一半以上是平台通道抛出的 PlatformException,比如原生侧提醒权限没开、数据库插件在后台被回收之类。另一半来自数据层空值处理,比如数据库查询字段为空时直接转 int 类型就会报错。建议在数据模型解析层统一做防御性判断,不要让空值一路穿透到 UI 再炸。
4.2 Future 异步与微任务队列的坑
Flutter 开发中异步编程是最容易写错又最难排查的部分,有人经常问“Future 的 then 回调是放在微任务队列里吗”,这个问题我自己在项目里体会极深。结论是:Future 的 then 回调会作为一个微任务被调度,当你调用 Future 的方法后,回调并不会立刻执行,而是要等当前事件循环同步代码执行完,再执行微任务队列。
这个知识点影响实际项目的地方在于,如果你在数据加载方法里先 await 了一个 Future,然后又同步修改了 UI 依赖的状态变量,UI 可能不会按你预想的顺序刷新。更隐蔽的是多个 Future 组合的时候,Future.wait 的执行顺序容易搞混。
我建议项目里统一用 async/await 而不是裸写 then 回调,因为可读性强,调试时栈信息也更清晰。另外,不要在 build 方法里直接发起异步请求,这会导致每帧 rebuild 都可能重新请求一遍数据,正确做法是把请求放到 initState 或者 Provider 的初始化方法中。
4.3 XTS 认证与 AAR 集成相关经验
如果你的 App 是要上架 OpenHarmony 生态的应用市场,就绕不开 XTS 认证。XTS 是 OpenHarmony 的兼容性测试套件,用来验证应用和设备是否符合系统兼容性规范。我在项目后期跑过一次 XTS 测试,遇到的问题多集中在权限声明和隐私合规层面。权限方面,OpenHarmony 对敏感权限的管控非常严格,凡是用不到的权限一律不要申请,尤其是在本地健康数据这类应用里,隐私声明和权限用途说明写不严谨就会被拒。
关于 flutter aar 集成,这个术语在 Android 工程里很常见,OpenHarmony 的混合工程也有类似的产物模式。在宿主工程里引入 Flutter module 编译出来的 aar 包时,要注意位次关系:Flutter module 主工程和宿主工程的 minSdkVersion 之间如果存在冲突,常常会表现为 native 库加载失败或者 MethodChannel 找不到实现类。排查这类问题最直接的突破口是把 native 日志打开,看 Flutter 引擎初始化到哪一步崩的,而不是盯着 Dart 层日志瞎猜。
4.4 高频问题快速排查表
| 现象 | 可能的原因 | 排查手段 |
|---|---|---|
| 新建 Flutter 工程后在 OpenHarmony 上跑不起来 | SDK 版本、Flutter 分支、DevEco Studio 三者版本不匹配 | 按设备系统版本反向查 Flutter 分支对应版本 |
| 页面滚动时明显掉帧 | 列表项未优化,或图表持续重绘 | 检查 const 构造、暂停不可见图表重绘 |
| MethodChannel 调用原生方法无响应 | 插件未注册或原生侧方法名拼写不一致 | 打开原生日志,确认引擎初始化完毕后再调用通道 |
| 药品库存变成负数 | 库存扣减逻辑分散、事务未使用 | 统一走数据层方法,并加上事务与库存预检 |
| 后台提醒不触发 | 应用进程被杀死,Timer 失效 | 使用系统提醒调度能力替代 Flutter Timer |
| sqflite 报 MissingPluginException | 插件版本不支持 OpenHarmony | 更换兼容 OpenHarmony 的数据库插件实现 |
这个表是自己项目跑出来的经验,不一定覆盖所有情况,但碰到类似症状时可以照着这个顺序排查,能省下不少时间。
最后说几句项目里的实在经验
整个项目做下来,我最大的感触是 Flutter 在 OpenHarmony 上的生态已经过了“能不能跑”的阶段,正在往“跑得好不好”的方向走。引擎分支、插件兼容、渲染性能这些都在肉眼可见地改善,但很多细节仍然需要用实际项目去趟坑才能摸清楚。比如 PlatformView 的兼容性、后台提醒的稳定性、XTS 认证对权限的严格要求,这些都不是看文档能提前避开的,只能靠真的把设备拿在手里试。
另外,家庭健康类工具这类 App,数据安全意识必须从第一天就有。本地存储虽然不给网络权限看似安全,但备份、导出、设备更换这些场景迟早会来。我建议至少在数据层预留一个加密能力接口,哪怕现在不启用,也不要等数据已经写了一大堆之后再做加密迁移,那个成本就太高了。
如果你也要做类似的项目,我最后再分享三个小的建议:第一,数据模型上一定要预留成员维度,哪怕现在只管一个人;第二,提醒功能优先用系统级的能力,不要依赖 Flutter 的 Timer 方案;第三,多准备几台不同版本的 OpenHarmony 设备做回归,纯靠模拟器和单一真机,很多兼容性问题根本复现不出来。这些坑我不希望你非得重新踩一遍才知道。