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

OpenHarmony上跑Flutter:家庭药箱管理App实战与踩坑记录

OpenHarmony上跑Flutter:家庭药箱管理App实战与踩坑记录 ★ FEATURED ARTICLE
家里药箱这东西不整理不知道一整理吓一跳过期半年多的感冒灵还在布洛芬只剩半板没人数得清老人每天该吃的降压药全靠猜。这些场景催生了我想做一个家庭药箱管理App的念头。正好手头在折腾flutter_for_openharmony这套方案索性把药箱管理、库存提醒、服药记录一次性落地。这篇文章就是这次实战的记录从方案选型到环境搭建从数据模型到状态管理再到一批让人头疼的坑都会按实际经历捋一遍。文章适合这么几类人看想在OpenHarmony设备上跑Flutter应用的开发者正在做健康管理类工具的独立开发者以及被服药记录怎么做这类需求卡住的朋友。我会把能直接复用的代码思路、表结构设计、Provider用法和排错经验放在正文里你照着抄大概率能少走弯路。1. 项目背景与方案选型1.1 为什么选择flutter_for_openharmony先回答一个很多人问的问题OpenHarmony OS是用什么语言编写的它不是一个语言写出来的底层内核和系统服务以C/C为主上层应用框架用的是ArkTS加上一部分TS和JS。ArkTS是OpenHarmony主推的声明式UI语言语法上有点像Flutter的Dart但生态和社区规模完全不在一个量级。那为什么不直接用ArkTS写这个药箱App我承认如果只是做几个表单页面ArkTS完全能胜任。但我的情况是团队里已经有现成的Flutter业务代码健康管理的核心逻辑写在Dart里本地数据库、路由、状态管理都是现成的。重新用ArkTS写一遍成本不只是语法切换还要重新处理组件通信、插件适配、数据持久化这些基础问题。跨平台复用的吸引力是我选择flutter_for_openharmony的最直接理由。需要说明的是这个方案不是谷歌官方那个Flutter仓库直接编译就能跑。OpenHarmony SIG组维护了flutter_flutter、flutter_engine和flutter_packages三套仓库经过适配后Flutter引擎才能在OHOS系统上原生运行。网上有人问新建项目为什么跑不起来多半就是没用对SDK拿着标准Flutter SDK去构建OpenHarmony工程目录下连ohos文件夹都没有能跑起来才怪。这套方案目前到什么程度我的体验是常用UI组件、Widget树渲染、Dart层业务逻辑都没问题核心插件也有移植版和原生体验有明显差距的地方在部分底层插件和极致渲染性能上。做工具型App完全够用做游戏或重交互应用需要再评估。1.2 药箱管理App的需求拆解家庭药箱管理真要做起来绝不是摆一个药品列表那么简单。我在动手前把需求拆成了四层第一层是药品信息管理。名称、规格、剩余数量、有效期、存放位置、归属人这些是最基础的字段。但要注意一个细节规格这个字段不能只写个字符串因为后面做剩余数量计算时需要知道一盒是24片还是两板12粒。我把规格拆成了包装数量和单位这样消耗操作时可以直接减掉一个包装单位。第二层是库存与有效期提醒。药品有效期是家庭药箱最容易出问题的地方很多家庭的药箱里都躺着过期药。我在列表页用剩余天数做排序和标签区分30天内到期标黄色过期标红色置灰。同时每次打开App都会扫描一遍如果有3天内到期或已过期的药品弹窗提醒。被动看列表和主动弹提示是两码事后者才是真正能防止误吃过期药的机制。第三层是服药记录这是整个App最核心的部分。记录不是存一条几点吃了什么药那么简单涉及到计划管理、按时提醒、补服标记、统计完成率是一套完整的执行链路。第四层是家庭成员管理因为药箱是家庭共用的必须能区分谁的药谁吃的药所以我加了一个owner_id关联字段每个成员可以独立看自己的用药记录。第一版我决定不做云同步。家庭药箱的数据量小本地数据库完全够用而且OpenHarmony设备和手机的同步机制还要额外适配不如先把本地闭环做好。2. 环境搭建与工程初始化2.1 OpenHarmony版Flutter环境配置环境这块踩的坑最多先按步骤写清楚。第一步拉取OpenHarmony专用Flutter SDK。这个SDK不在官方flutter仓库里而是从OpenHarmony SIG的flutter_flutter仓库拉取还要对应你目标设备的OpenHarmony版本。我当时用了一块orangepi5pro开发板系统是OpenHarmony最新稳定版SDK分支也要跟着选匹配的tag。版本不匹配的表现是启动App直接崩日志里一堆So库加载失败非常劝退。第二步装DevEco Studio它的作用类似Android开发中的Android StudioOpenHarmony应用的构建、签名、烧录都靠它。装完后配置好SDK路径让flutter doctor能识别到OHOS Toolchain。如果doctor输出里没有OHOS相关项说明DevEco Studio的SDK路径没配对或者版本太旧。第三步创建项目。执行flutter create命令后目录结构会和标准Flutter项目基本相同只是多了一个ohos目录这个目录就是OpenHarmony工程的壳子。注意网上搜flutter新建项目后跑不起来的人大部分是卡在这一步虽然创建了项目但没有正确构建ohos壳子生成hap安装包。命令行flutter run -d不一定能识别OpenHarmony设备我的做法是先用DevEco Studio构建hap包安装到设备再手动启动Flutter引擎配合日志查看运行状态。还有一个细节体会OpenHarmony的Flutter开发调试体验目前确实不如Android流畅。热重载能生效但有时第一次启动要等好几秒设备端日志也有延迟。如果遇到you are applying flutters main gradle plugin imperatively using the apply这类Gradle构建报错不要慌这是Flutter对Gradle插件应用方式的改变导致的沿着构建脚本检查插件应用顺序即可和OpenHarmony没有直接关系。2.2 项目结构与依赖管理项目骨架里lib目录放Dart代码和标准Flutter项目一模一样。ohos目录放OpenHarmony工程配置包括权限声明、应用图标、模块级配置。权限声明这里值得展开药箱App用到了本地存储、通知提醒两个关键权限需要在ohos的module.json5里声明。如果漏了通知权限服药到点不弹提醒体验直接打折扣。依赖管理还是pubspec.yaml但有个重要注意事项不是所有pub.dev上的包都能在OpenHarmony上直接用。凡是依赖原生Android或iOS通道的插件都需要检查是否已有OpenHarmony适配版。比如sqflite官方版用的是Android的SQLite实现在OpenHarmony上跑起来会找不到原生方法。正确做法是用SIG维护的flutter_packages仓库里的适配版API几乎一致把import路径换一下再调整pubspec里的依赖地址就能用。选依赖的原则我总结了三句优先纯Dart实现其次找官方适配版最后才考虑自己写平台通道。以这个项目为例状态管理用的provider是纯Dart包直接用没问题数据库用适配后的sqflite图表库我干脆没用第三方自己用CustomPaint画折线图和环形图规避了字体渲染兼容问题。3. 家庭药箱核心功能设计3.1 数据模型与表结构药物数据模型的设计我吃了两次亏才定下来。一开始想当然地设计了medicine表只有名称、规格、数量、有效期四个字段。结果做到成员管理时发现药不知道是谁的记录也无法按人统计。后来加了owner_id把家庭成员独立成表才算理顺。数据库选的是SQLitesqflite适配包操作起来和标准Flutter完全一致。药品表的字段如下medicine_id主键自增长name药品名必填加索引方便搜索spec_count包装内数量比如一盒两板就是2spec_unit包装单位板/瓶/盒unit_dosage最小单位含量比如0.25gstock_count剩余最小单位数量expiry_date有效期存yyyy-MM-dd字符串排序时直接比较即可location存放位置家庭药箱多个抽屉时需要这个字段owner_id归属成员关联家庭成员表服药计划表是另一个核心字段包括plan_id、medicine_id、owner_id、dosage、frequency_type、time_points、start_date、end_date、enabled。frequency_type设计成枚举一天一次、一天两次、一天三次。time_points存一个JSON字符串比如[08:00,12:00,18:00]这样每个计划可以灵活定义服药时间点。这个设计比我最初用cron表达式要直观得多也更好处理补服的场景。为什么要强调把表结构先想清楚因为药物记录这类数据一旦积累起来中途改表结构很痛苦。我的经验是宁可多花半小时画清楚字段关系也不要写几行代码就急着跑。3.2 药品录入、库存与过期提醒录入页我用了一个滚动表单药品名和规格是必填项剩余数量和有效期默认给一个合理初始值减少用户输入负担。规格那里我加了常用候选比如片、粒、袋、毫升配合输入框比单纯键盘打字快很多。库存管理的操作逻辑要站在用户角度想。消耗这个按钮默认减1遇到整板整盒的情况可以长按弹出数字选择器一次性减掉一板。每次库存变化都写入stock_log流水表字段包含medicine_id、change_count、operation_type消耗/补充、时间戳。流水的好处后期会体现出来可以回答这盒药到底吃了多少什么时候买的这类问题。过期提醒用了两层机制。第一层是列表页的视觉标识按剩余天数排序30天内到期黄色过期红色。第二层是App启动时扫描全表如果有3天内到期或已过期的药品弹Dialog提示。为什么两套都要因为用户不是天天打开App的如果只在列表里加标识很可能直到吃了过期药都没注意到。启动弹窗虽然粗暴但确实能逼着用户处理。3.3 页面结构与导航组织页面结构走的是底部导航三页方案药箱、服药记录、我的。药箱页是药品列表支持按位置、按用药人筛选服药记录页默认显示今天的待服和已服记录可以切按天、按周、按人查看我的页放家庭成员管理和设置成员信息包括头像、姓名、角色成年人、老人、儿童角色会影响UI上字体大小和提醒文案风格。这个结构没有做复杂的嵌套也没上侧边栏。家庭药箱的核心操作就三个找到药、记录吃药、查看有没有过期。导航越简单越不容易点错尤其对老年用户来说层次太深很难用。Provider管理的全局状态包括当前选中成员、药品列表缓存、服药计划的今日任务集这些状态在三个Tab之间共享切换页面能保持实时同步。4. 服药记录功能实战4.1 服药计划的创建与执行服药记录是本项目的灵魂我花了两周时间设计和迭代这个功能。先说创建流程。用户从药品详情页进入创建服药计划依次选择用药人、药品、单次剂量、频次、用药起止日期。保存后计划进入一张plan表enabled置为true。关键点在执行层。我没有预先为整个周期生成几千条服药记录而是设计了一个按天生成的机制每天凌晨检查所有enabled计划为每个计划生成当天的执行记录。每一条记录包含plan_id、medicine_id、owner_id、scheduled_time、status、actual_time。status定义四个值pending待服用、taken已服用、late补服、missed遗漏。这个当天生成的设计好处是计划中途修改不会留下脏数据。比如医生调整了剂量如果预先生成了全年记录那改起来就是灾难。按天生成意味着今天的变更只影响今天的执行昨天的历史保持原样。这个思路其实是从任务调度系统里借鉴的小项目也能用上大项目的设计智慧。每天在特定时间点比如早上8点、中午12点、晚上6点系统会检查当期记录的状态如果有pending记录通过本地通知提醒用户。OpenHarmony的通知api和Android不同我封装成了一个NotificationHelper后续换平台时只改这个类。4.2 打卡交互与记录查询打卡界面做成了时间线形式。今天的服药记录按时间排列每条卡上显示药品名、剂量、计划时间。当前状态是pending时右侧是一个圆形打卡按钮点击后变成已服用状态并把当前时间写入actual_time。如果过了计划时间2小时还没打卡状态会自动标记为late但仍允许补打卡只是UI上会显示一个晚点标签方便用户直观看到自己是不是没准时吃药。补打卡逻辑有一个细节如果用户早上忘了中午想起来补打这时候戳的是早上那个计划还是中午这个计划我的设计是每条记录独立打卡用户必须在时间线上找到对应那条。好处是记录真实坏处是操作稍繁琐。为了平衡我在卡片上加了设为已服的快捷操作默认打卡时间为当前时间允许手动修改为任意时间。这样既能补录又能保证记录的准确性。查询维度做了三个筛选条件按用药人、按日期区间、按具体药品。三个条件可以自由组合组合出来的列表就是2024年5月期间家里老人都吃了哪些药。这个查询对家庭健康管理很有价值能直观看出某人某段时间的用药情况后续如果想做健康报告数据基础已经具备了。4.3 统计图表与数据分析统计页放了三个指标本周应服次数、实际服用次数、完成率。数据来源于两条SQL聚合查询应服次数按计划中的frequency和天数计算实服次数从执行记录表里统计。完成率就是两者相除。图表实现起初想起用fl_chart但仔细查了OpenHarmony适配情况发现这类依赖Canvas绘制的图表库在OpenHarmony Flutter引擎下中文标签偶尔显示异常。虽然是小概率问题但我不喜欢在关键页面放不稳定因素所以用CustomPaint手绘了一个7天完成率折线图和当日各时段环形图。代码量大概300行换来的是完全可控的显示效果。这里有统计口径的边界问题如果计划中途调整完成率怎么算我用了比较简单的逻辑统计时以当前计划为基准计算应服次数历史实服次数不变。这样可能造成前几天超额与后几天不足的偏差但对家庭日常使用来说可接受。毕竟这个App的定位不是临床试验系统偏差在5%以内完全不影响判断最近吃药是否规律。5. 组件通信与状态管理5.1 Provider在项目里的实际用法flutter provider怎么用是高频搜索词我也在这里分享项目里的实际用法。为什么选Provider而不是Bloc或Riverpod这个项目规模不大状态类型也就药品、计划、成员三大类用Bloc那套事件流反而重。Provider的ChangeNotifier机制足够表达状态变化时通知监听者UI自动刷新。我在lib/state目录下建了MedicineStore、PlanStore、MemberStore三个ChangeNotifier。以MedicineStore为例它维护药品列表缓存、当前选中药品、统计信息。初始化时从数据库全量加载之后所有增删改操作都通过store的方法执行store内部先更新内存再异步写数据库最后notifyListeners通知UI。使用时有几个容易踩的坑。Provider.of(context)默认是会监听变化的如果你只是调用一个方法不要用带监听的写法否则这个Widget会因为无关状态变化而反复rebuild。正确姿势是Provider.of(context, listen: false)获取实例来调用方法。另一个是Context跨层问题我用Consumer包裹需要响应变化的最小Widget避免整棵子树重建。这两个习惯养成后你会发现即便状态多几个UI依然能保持流畅。5.2 Flutter组件通信的四种方式组件通信这个点我特意做了个总结。Flutter里组件通信无非四种途径父传子用构造参数、子传父用回调、跨层共享用InheritedWidget或Provider、跨组件事件用EventBus。药箱项目里这四种都用到了。药品列表的ListItem组件通过构造参数接收Medicine对象这是最基础的父传子。卡片上的编辑消耗按钮通过onEdit和onConsume回调上抛给页面处理。跨层共享是Provider的主场库存数量变化后列表页、详情页、统计页需要同时刷新这种一改全动的场景用setState根本管不过来。最后一个EventBus我在路由跳转时传递复杂对象会用到注意选择纯Dart实现的event_bus包因为OpenHarmony上任何涉及平台通道的包都可能成为隐患。给新手一个建议不要一上来就上重状态管理框架。先想清楚数据流方向大部分组件通信其实用参数和回调就够了。状态管理是解决跨页面共享可变状态的不是解决父子组件传值的。很多项目把简单问题复杂化就是因为分不清这两者的边界。5.3 ArkTS与Flutter的生态对比这个话题搜的人也多ArkTS和Flutter谁更流行。如果有人现在用流行来决定技术选型我觉得有点本末倒置。在OpenHarmony生态里ArkTS是官方主推的系统原生UI语言跟着官方文档走对纯OpenHarmony应用来说是首选性能上限更高系统能力调用也方便。但它的社区、第三方库、学习资料和Flutter相比还差一个量级。Flutter的优势在于跨平台复用和组件生态。一套Dart代码覆盖Android、iOS、Web、Windows、Linux、OpenHarmony这笔账对商业项目很划算。我的判断是两者会长期并存工具型App、内部管理系统选Flutter性价比高对系统集成度要求高、只跑OpenHarmony终端的场景ArkTS更稳妥。顺带说一句Flutter渲染引擎Impeller。Android上Impeller已经带来很好的渲染性能但在OpenHarmony上的适配还在完善中。所以如果你在OpenHarmony设备上遇到某些动画卡顿或文字渲染异常先往引擎差异方向排查不要急着改业务代码。等适配成熟后这些渲染问题大概率会自行消失。6. 常见问题与排查经验6.1 编译与运行崩溃排查把我在实际开发中遇到的高频问题整理成一个速查表方便遇到类似报错的人快速定位。第一类新建项目跑不起来。原因通常是三种SDK版本和设备系统不匹配ohos壳子没正确构建设备没开开发者模式。排查顺序先确认DevEco Studio能编译出hap并在设备安装再查Flutter SDK分支。如果项目目录里没有ohos目录说明你用的还是标准Flutter SDK换OpenHarmony分支重试。第二类e/flutter开头的Dart VM初始化错误。日志长这样[error:flutter/runtime/dart_vm_initializer.cc(41)] unhandled exception。这一长串路径看起来很吓人本质就是Dart运行时遇到了未捕获异常。在OpenHarmony上最常见的原因是插件适配不全某个MethodChannel调用返回了空值。解决办法是全局注册FlutterError.onError通过Dart堆栈定位到具体代码段再排查该处是否调用了不兼容的插件。第三类Gradle插件应用方式报错。如果在构建阶段看到you are applying flutters main gradle plugin imperatively using the apply开头的提示是Flutter新版本和Gradle配置不匹配导致的。检查项目根目录的settings.gradle和应用模块的build.gradle把插件声明改成plugins DSL方式。这个和OpenHarmony本身无关标准Flutter项目也会遇到。第四类So库加载失败崩溃。这通常是Flutter引擎和系统API level不匹配。在Android上是abi问题在OpenHarmony上多半是版本分支不对。重新检查flutter_flutter仓库的tag和设备系统版本找一个匹配的组合。6.2 插件适配与数据库选型插件兼容性排查库尽量列一下我用到的和踩过的插件用途OpenHarmony适配状态sqflite本地数据库有适配版需改依赖provider状态管理纯Dart可直接用intl日期格式化纯Dart可直接用camera拍照适配不完善暂未用fl_chart图表绘制有兼容问题未用shared_preferences轻量存储有适配版可用sqflite是重头戏。标准sqflite走Android的SQLite实现在OpenHarmony上会报九十九找不到NativeImplementation。使用适配包后数据库API基本一致唯一要注意的是路径处理数据库文件存放路径和Android不完全相同我封装了一个DatabaseHelper类统一管理避免散落的openDatabase调用改起来痛苦。camera插件我单独提一下因为热搜里有openharmony camera。做药品拍照识别这个功能OpenHarmony侧的相机Kit和Flutter侧的camera插件之间需要搭桥官方适配还没成熟。我的建议是第一版别做实时预览加一个调用系统相机拍照的入口拿回图片再处理可以大幅降低开发难度。如果非要做App内实时预览先确认设备的相机能力和OpenHarmony版本。6.3 渲染与性能优化细节OpenHarmony开发板这类设备的GPU能力有限我在渲染优化上积累了三条经验。第一条列表分页。药箱记录少的时候不觉得一旦加入流水日志几千条数据一次性加载会让列表卡顿明显。采用分页加载每页20条配合ListView.builder懒加载滑动立刻顺滑很多。第二条避开重阴影和模糊。Flutter的Container可以很方便地加BoxShadow但在OpenHarmony设备上大范围阴影会明显拖慢合成速度。我后来把卡片阴影改成了细边框加浅色背景视觉层次没怎么减性能提升明显。模糊效果同理BackdropFilter在低端设备上是性能杀手。第三条图片加载优化。如果药品加了照片或家庭成员有头像用cached_network_image这类缓存库时要设置cacheWidth。不设的话原图可能是一张1920像素的图缩略显示时白白消耗了解码开销。设成200像素内存占用能降低好几倍。这个优化在低端设备上感知特别强。6.4 调试手段与日志采集OpenHarmony调试和Android有个明显的差异日志系统是独立的。我用三个途径配合排查问题。一是DevEco Studio的HiLog窗口能看系统侧日志比如权限拒绝、So库加载失败这类信息二是Flutter的debugPrint输出定位Dart层状态三是串口日志在开发板上调试时串口输出往往比IDE日志更及时。实测下来debugPrint在某些OpenHarmony设备上延迟严重关键状态流转建议用HiLog封装一层。我写了一个LogHelper内部用MethodChannel调系统日志接口这样关键信息能第一时间在DevEco的日志窗口看到。热重载功能在OpenHarmony上是能用的但偶尔会有偶发失效。遇到改代码没生效的情况先试全量重启不要浪费时间排查热重载状态。最后再分享一个体会。家庭药箱这种工具型App技术方案其实不是最大的瓶颈真正决定用户是否坚持使用的是细节药品快过期时有没有醒目提示打卡是不是足够顺畅家庭成员切换是否顺手。多站在实际使用场景里想问题比纠结某个按钮该用Provider还是setState要重要得多。如果这个项目后续要做二期我会优先加两个东西一个是家庭成员间共享药箱数据的云同步一个是基于用药记录的周报自动生成。功能不多但每一个都在把工具变成家庭健康管理助手。
阅读完成 · 觉得有帮助?
咨询建站