1. 项目背景与系统设置的模块定位
1.1 为什么是Flutter + OpenHarmony
先聊聊这个项目是怎么来的。智慧养老这个方向,说白了就是给老年用户做一套能用、愿意用的终端应用。但在实际落地时,第一个卡住团队的问题不是业务逻辑,而是“这套App到底跑在什么设备上”。养老场景里常见的是智能大屏、机顶盒、带屏音箱、健康一体机,这些设备的操作系统五花八门,如果每个平台都单独做原生开发,后续维护成本直接翻倍。
选择Flutter for OpenHarmony,最核心的原因是“一次编写,多端运行”。团队原本就有Flutter的开发经验,而OpenHarmony生态的设备这两年在家电、安防、适老改造领域增长得很快。与其等生态完全成熟再进场,不如先把Flutter在OpenHarmony上的坑趟一遍。这个项目的目标明确:在OpenHarmony设备上跑起一套包含健康数据、消息提醒、系统设置三大模块的养老App,其中系统设置模块是最能验证“跨端能力”的部分,因为它在交互、权限、存储、通知等多个维度都有覆盖。
1.2 系统设置模块在养老App里的定位
很多人觉得系统设置不就是几个开关加一个列表页吗?真正上手之后才发现,这个模块是整个App里最容易做“糙”的部分。老年用户的典型使用场景是:看不清楚字、容易误触、需要依赖语音或大字体、对权限和通知的感知特别弱。所以这个系统设置模块不是给极客用的,而是给家里长辈用的,它的定位应该是“把复杂的技术决策藏起来,只给用户最简单的选择”。
功能上划分成几个子模块:个人信息维护(监护人联系方式、紧急联系人)、健康数据管理(心率、血压的测量单位与提醒阈值)、通知与提醒设置(服药提醒、异常告警)、显示与无障碍设置(字体大小、语音播报开关、屏幕常亮)、关于设备(系统版本、存储空间清理)。这些功能在普通App里就是一堆输入框和开关,但在OpenHarmony + Flutter的体系下,每一项都要考虑原生能力的对接,尤其是路由、权限、存储和通知服务。
这个模块的另一个重要职责是“兜底”。老人的设备如果出现异常,比如蓝牙断连、网络不通、字体被误调成超大,系统设置页应该是用户第一个能找回安全感的地方。所以在设计上我会刻意把“恢复默认设置”“一键呼叫帮助”这类入口放在更显眼的位置,而不是藏在列表深处。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境搭建与工程配置
2.1 OpenHarmony SDK与Flutter SDK的版本匹配
先说环境。Flutter for OpenHarmony目前并不是Flutter官方主线直接支持的,而是基于OpenHarmony的flutter_flutter仓库fork版本。初次接触这个组合的人最容易在这步耽误时间,因为版本匹配是一个“牵一发动全身”的问题。
我的建议是直接使用OpenHarmony官方推荐的SDK组合。以当前比较稳定的版本为例,OpenHarmony SDK选择4.0 Release或更新的5.0版本,Flutter SDK使用OpenHarmony分支的3.7.x或3.10.x版本。这里有个关键点:不要用Flutter官方主线的SDK去尝试编译OpenHarmony工程,那会直接报错。因为OpenHarmony分支的Flutter引擎层做了平台通道的替换,把原来对接Android/iOS的embedder改成了对接OpenHarmony的ACE(Ability Cross-platform Environment)框架。
安装完成后,记得检查环境变量。我在macOS上配置时,需要把ohos-sdk的toolchain路径加到PATH中,同时在local.properties里指定ohosSdk目录。如果路径不对,构建时只会报一个模糊的错误,比如“Unable to locate Ohos SDK”,这时候先别急着重装,检查路径是不是写对了。
注意:Flutter for OpenHarmony的构建产物是HAP包,不是APK。所以你的Flutter工程里除了pubspec.yaml,还需要一个build-profile.json5文件来配置签名和模块信息。这部分很多人会漏掉。
2.2 创建Flutter工程并切换到OpenHarmony平台
工程创建这一步相对简单,可以用flutter create命令生成一个标准的Flutter工程,但生成完之后需要做两件事:一是添加ohos目录,二是修改模块配置。
实际上,OpenHarmony分支的flutter create命令已经支持ohos平台。执行时直接指定:
code复制flutter create --platforms=ohos my_app
这样会生成带ohos平台的工程结构。如果你用默认命令生成后再手动加ohos目录,会麻烦一些,因为需要自己写module.json5和build-profile.json5。
不过我这里有个建议:头一次跑通整个流程,最好先用官方仓库里自带的example工程验证环境,而不是直接拿业务工程去试。原因很简单,example工程里的依赖版本、Gradle配置、签名文件都是对齐好的,只要它能跑起来,说明环境没问题。我当初图省事,直接用业务代码去调环境,结果分不清是代码问题还是环境问题,排查耗时翻了三倍。
配置好之后,执行flutter build hap --debug构建调试包。第一次构建会下载大量Gradle依赖,这个过程的耗时取决于网络状况,建议预留半小时以上。遇到Gradle下载慢的情况,可以手动配置Gradle镜像仓库,但这里不展开讲代理配置,只提一句:在build-profile.json5里可以指定国内镜像源,能明显提升下载速度。
2.3 热重载与调试工具链
Flutter开发最爽的就是热重载,这个特性在OpenHarmony上也是支持的,但前提是使用OpenHarmony分支的Flutter工具链。启动调试的方式是:
code复制flutter run -d <device_id>
设备ID可以通过flutter devices查看。OpenHarmony设备连接后,会显示为ohos类型。热重载的快捷键(r键)和官方Flutter保持一致,但实测在OpenHarmony上热重载的稳定性不如Android,尤其涉及平台通道修改时,偶尔会失效,这时候老老实实执行R键冷重启,不要浪费时间反复触发热重载。
调试日志我推荐用OpenHarmony自带的hilog工具,它比adb logcat的过滤方式更适合鸿蒙系统。查看Flutter相关日志的过滤命令是:
code复制hilog | grep flutter
另外,DevEco Studio集成的Profiler工具能查看引擎层的帧渲染耗时,对排查卡顿问题非常有帮助。在养老设备这类低配硬件上,这个工具几乎是必备的,后面讲性能优化时还会再提。
3. 系统设置模块的页面结构与核心组件
3.1 整体导航与页面路由设计
系统设置模块虽然功能多,但在页面组织上要遵循“少层级、大触控”的原则。老年用户几乎没有“面包屑导航”的概念,也不应该让他们记住自己从哪个页面进来的。所以我在路由设计上采用扁平化结构:一级页面是设置首页,所有功能入口都直接展示在上面;二级页面只有极少数需要复杂交互的功能,比如编辑紧急联系人、调整提醒阈值。
路由管理我用的方案是Flutter官方的Navigator 1.0,再加上一个简单的路由常量表。没有引入fluro或go_router这类第三方库,原因有两个:一是这个模块的页面数量有限,路由跳转基本是固定场景,不需要复杂的动态路由参数;二是OpenHarmony平台上第三方路由库的适配程度参差不齐,与其花时间调试,不如用最稳妥的原生方案。系统设置这个场景里,稳定压倒一切。
路由表大概这样设计:
code复制class AppRoutes {
static const String settingsHome = '/settings';
static const String profileEdit = '/settings/profile';
static const String contactEdit = '/settings/contact';
static const String healthUnit = '/settings/healthUnit';
static const String notifyConfig = '/settings/notify';
static const String displayConfig = '/settings/display';
static const String aboutDevice = '/settings/about';
}
3.2 界面适配:大字体与高对比度
适老化设计不是简单把字号调大,而是从组件层面做深度适配。Flutter的TextScaler机制在OpenHarmony上是可以正常工作的,但我的踩坑经验是:不要全局直接套用MediaQuery.textScalerOf(context),因为某些自定义组件(比如固定高度的按钮、横向排列的标签)在文字放大后会溢出。
系统设置页面的实现,我采用了自研的SettingItem组件。它的核心结构是一个Container包裹的Row,左侧是图标和标题,右侧是当前值或开关。为了适配大字体,这个组件不能使用固定高度,而要用最小高度加上padding来约束,让文字撑开时能自动增高。
对比度方面,我用了深灰文字配浅灰背景,再给可点击项加上一条细的分隔线。开关组件的on/off状态,除了颜色区分,还额外用文字标签“开/关”辅助,避免色弱用户看不清。这样的小细节,在真正面对老年用户时价值极大。
3.3 与开源组件库的兼容性
在组件库的选择上,我踩过一个坑:直接把Flutter社区热门的库(比如flutter_switch、settings_ui)拿过来用,结果在OpenHarmony上报错。因为这些库内部可能依赖了Flutter官方主线的某些平台实现,在OpenHarmony分支上并不一定兼容。
排查这类问题有个通用方法:打开debug模式,看报错堆栈里是否出现了MissingPluginException或UnimplementedError,如果有,基本可以判定是插件层面的兼容问题。实际项目中,setting页的开关、滑杆、输入框这些组件,我最终选择用Flutter基础组件(Switch、Slider、TextField)自己封装,虽然代码量会增加一些,但可控性完全不一样。
这里顺便说一下checkboxlisttile这个组件。有同行在群里问Flutter的CheckboxListTile文字和勾选框之间的距离怎么调,因为默认间距在放大字体后显得局促。这其实不只在OpenHarmony上会出现,在Android上也会。解决方法是不要用ListTile,而是自己用Row加Expanded包裹Text和Checkbox,间距就可以自由控制了。我在系统设置里凡是涉及多选配置(比如服药提醒的重复星期)的地方,都用自定义Row替代了CheckboxListTile。
3.4 页面主题与License页面颜色
Flutter应用的ThemeData在OpenHarmony上是生效的,包括ColorScheme、TextTheme等。但有一个细节值得注意:设置页里的“关于”页面会显示开源许可(License)列表,Flutter默认的showLicensePage页面主题颜色不是跟随Material 3的,而是走的是默认蓝灰色调。想在OpenHarmony上让这个页面跟随App主题,我用的方法是直接在showLicensePage外层包一层Theme:
code复制Theme(
data: Theme.of(context).copyWith(
appBarTheme: AppBarTheme(
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
),
),
child: const LicensePage(),
)
这个技巧虽然简单,但能避免用户在“关于”页面看到一个风格完全不一样的界面。在给客户演示时,这种细节往往比核心功能更容易获得好感——因为他们直观感受到的是“整个App是统一的”。
4. 关键功能模块的实现与平台通道对接
4.1 通知与提醒服务
系统设置里的“服药提醒”功能,是养老App的刚需。这块的技术难点不在UI,而在于“定时通知”能力。Flutter本身不提供后台定时任务的跨平台方案,Android上有workmanager插件,OpenHarmony上则需要对接系统的通知服务。
OpenHarmony的通知服务接口通过NotificationManager提供。从Flutter侧调用,有两种方式:一是使用官方或社区已有的插件,二是自己写平台通道。我的建议是:如果只做基础的通知能力,优先找社区插件;但如果要做的功能涉及复杂的交互(比如通知直接拉起App的指定页面),就得自己写平台通道。
平台通道的写法跟Android上几乎一样。在Flutter侧定义MethodChannel,在OpenHarmony侧通过ets写一个继承自Ability的类来接收消息。实际代码类似这样:
Flutter侧:
code复制const platform = MethodChannel('com.example.eldercare/notify');
await platform.invokeMethod('scheduleNotification', {
'id': 1,
'title': '服药提醒',
'content': '请按时服用降压药',
'timestamp': DateTime.now().add(Duration(minutes: 5)).millisecondsSinceEpoch,
});
OpenHarmony侧用ArkTS处理这些参数,再调用NotificationManager的接口。这个通道调试的时候,最常遇到的问题就是类型不匹配。Flutter侧传的int,到ArkTS侧可能被解析成number,但某些API要求的是long,所以我们在桥接层统一做一次Number转换,不要直接透传。
4.2 存储与偏好设置
系统设置里的用户配置项,比如字体大小、提醒开关、联系人信息,需要一个本地持久化方案。Flutter开发的场景里,大家习惯用shared_preferences插件。好消息是,OpenHarmony社区已经有人移植了shared_preferences的鸿蒙实现,大部分基础场景可以直接用。
但我的实践发现,shared_preferences在OpenHarmony上底层存取的是Preferences数据库,适合轻量键值对,如果要存复杂结构(比如一整份健康档案),还是建议使用轻量级数据库或文件存储。系统设置模块里的健康数据,我用的是OpenHarmony的分布式数据服务(Distributed Data Service),这个接口能天然支持设备间同步——老人家里的设备和大屏之间数据一致性有保障。
数据存储这块有个细节问题需要注意:Flutter的shared_preferences在OpenHarmony上的异步写操作偶尔会有延迟,如果用户在设置页改完配置后立刻杀掉App,最后几次修改可能没有落盘。我的处理方式是在修改关键配置项时,在页面生命周期里增加等待机制:用户点击返回时,先等持久化操作完成再允许页面关闭。具体用async/await控制,实测能有效减少数据丢失。
4.3 权限申请与蓝牙设备管理
养老场景里,血压计、心率带、智能手环基本都是通过蓝牙连接。所以系统设置里必须包含“设备管理”子项,允许用户查看当前已连接的设备、信号强度、电量,并能断开或重新配对。
OpenHarmony上的蓝牙权限有几个层级:BLUETOOTH权限、定位权限(因为蓝牙扫描涉及位置信息)、以及后台运行时需要申请的LOCATION权限。Flutter侧申请权限可以用permission_handler插件,但这个插件在OpenHarmony上目前还不完善,需要手动检测权限状态后通过平台通道申请。
更麻烦的是,OpenHarmony上蓝牙扫描回调的数据结构跟Android有差异。我在对接时发现,底层返回的ScanResult中,某些字段(比如设备类型)在OpenHarmony上可能是空值,而业务代码如果直接string解包会崩溃。处理方式是在平台通道侧做一层容错:所有从底层返回的字段,先做非空判断再序列化传给Flutter。
4.4 拉起支付与微信登录
智慧养老App虽然面向老人,但最终的付费方往往是子女。所以在“我的-家庭服务”里,会有一个支付入口,同时也是微信登录的绑定入口。这部分功能在Flutter for OpenHarmony上会遇到的坑很有代表性。
先说拉起支付。HarmonyOS原生的支付SDK是基于Ability调用的,Flutter侧拉起的方式跟Android的Intent类似,但需要额外处理回调。常见的错误是:支付结果回来后,Flutter端收不到任何消息。这通常是因为OpenHarmony的AbilityResult回调需要自己注册监听器,不像Android那样在onActivityResult里就有现成的回调入口。
我的实现方式是:在OpenHarmony侧的MainAbility中重写onAbilityResult方法,将回调数据通过EventChannel推送给Flutter侧。这样Flutter不管支付结果是成功、失败还是取消,都能收到事件。一个值得注意的点是,这个EventChannel需要在Flutter初始化完成后再注册,否则会出现消息丢失。我踩过这个坑后,索性在Flutter层做了一次“启动后3秒内注册监听+本地缓冲最后一次结果”的双保险。
再说微信登录。严格来说OpenHarmony官方并没有微信登录的SDK,但华为应用市场提供了一个兼容方案:通过“华为账号服务”间接实现一键登录,或者使用微信开放平台在HarmonyOS Next上的适配版本。具体对接时,主要是处理签名匹配问题。微信SDK在拉起前会校验应用签名,如果OpenHarmony上的签名证书格式与Android不一致,微信端会直接拒绝拉起。解决方法是:在加固/签名阶段统一使用符合微信要求的证书,并在测试时用wechat的debug工具查看具体错误码。
上面这两个功能,本质上是“在OpenHarmony的Ability机制与Flutter的异步事件模型之间建一座桥”。如果团队之前只做过纯Flutter开发,没有原生层的经验,这部分很容易卡住。我建议在项目中设置一个“平台能力抽象层”,把所有跟硬件、系统服务相关的调用都封装好,Flutter侧只负责业务逻辑,避免业务代码直接散落MethodChannel调用。
4.5 显示与无障碍:字体、锁屏与语音播报
字母大小设置、屏幕常亮、语音播报这三项,是系统设置里老年人最常用的。字体大小的实现我采用了“全局缩放因子”方案:在MaterialApp的builder里,读取设置页保存的textScaleFactor,然后传给MediaQuery。相比用ThemeData的textTheme来统一改,这种方法可以更彻底地影响所有继承自MediaQuery的组件,包括系统原生对话框和通用插件页面。
屏幕常亮的功能需要调用平台能力。Flutter里可以用wakelock_plus插件,但这个插件在OpenHarmony上的适配还不太成熟。我的做法是写一个极简的平台通道:Flutter侧调用keepScreenOn方法,OpenHarmony侧获取Window实例后执行setWindowKeepScreenOn。实测在RK3568的开发板上,这个方法比第三方插件可靠得多。
语音播报这块,早期版本我打算用flutter_tts,但OpenHarmony社区对TTS引擎的支持要么是调用系统语音合成能力,要么就得接第三方云服务。考虑到系统设置模块只是在“操作确认”时需要短暂播报,我用的是OpenHarmony的textToSpeech接口,通过平台通道把文本传下去。虽然音频风格有点像以前的地铁报站音,并不自然,但对于提醒场景已经够用。
5. 性能优化与多设备适配
5.1 RK3568的设备树与低配设备适配
如果你部署的OpenHarmony设备是基于RK3568开发板(这是当前最常见的OpenHarmony硬件平台),可能会遇到一个专门的问题:同一个开发板存在多份设备树(DTS)配置文件,到底怎么选。很多人第一次拿到板子,烧录系统的时候不知道启动参数里该填哪个dtb,结果要么屏幕不亮,要么WiFi无法识别。
我的经验是:不要从dtb文件名猜硬件版本,先去查板子的实际配置。通常出厂文档里会标明使用的屏幕型号、触摸芯片型号和WiFi模组型号,然后对照源码里的dts目录找匹配项。如果文档缺失,一个通用的方法是先烧录系统后在串口终端执行dmesg | grep -i "Unable to parse",看驱动加载时找不到哪个设备节点,这能帮你快速定位需要哪个dtb。当然,选择适合自己的dtb只是迈出第一步,因为实际开发中真正影响应用性能的往往不是dtb而是板子的内存和GPU。
在RK3568(通常是2GB内存)这类设备上跑Flutter应用,启动速度是第一道坎。Flutter的初始化需要加载引擎库和Dart虚拟机,在低配设备上可能要花3到5秒。为了优化启动体验,我的做法是在MainActivity的onCreate阶段先显示一个OpenHarmony原生的启动图,同时把Flutter引擎的预加载提前到Application的onCreate里做。这样等用户看到Flutter首页时,引擎已经初始化完毕,体感时间能缩短一半。
5.2 减少掉帧与内存占用
设置页面有一个典型卡顿场景:页面中同时存在多个Switch开关和动态列表项时,稍微复杂的动画或者键盘弹起,就会出现掉帧。在RK3568上,这种卡顿更明显。我的排查方法是先用DevEco Studio的Profiler工具抓一帧的构建耗时,发现主要瓶颈在“过多的全局Key”和“不合理的setState范围”。
针对上帧,我做了两个优化:
第一,列表项用const构造。凡是父组件布局不依赖子组件内部状态的地方,都声明为const,让Flutter在编译期就能复用组件实例。这里的收益非常明显,尤其当设置列表超过8项时,帧率会有明显的提升。
第二,避免在build方法里重复创建大型对象。比如日期格式化器、颜色映射表这些,提为类成员变量,不要写成局部变量。这点看起来是基础常识,在性能监控抓log时,我发现dart代码里的隐式分配比想象中更影响低端设备的帧时间。
内存方面,OpenHarmony的后台应用回收机制和Android不完全一样。我实测发现,Flutter App在切到后台后,如果30分钟内没有重新进入前台,再次打开时经常会出现白屏或黑屏,原因就是OpenHarmony回收了Ability的内存,但Flutter引擎的恢复却失败了。解决这个问题有几个层面:一是监听系统内存压力回调,及时清理非关键缓存;二是在Flutter侧缓存页面状态时,优先使用ValueNotifier等轻量方案,避免直接持有BuildContext。
5.3 屏幕适配与横竖屏策略
智慧养老App最常见的部署场景是平板和大屏。但OpenHarmony这个生态里,各个厂商的屏幕尺寸真的非常割裂:有7寸的平板,有10寸的带屏音箱,还有21寸的社区触摸屏。所以系统设置模块的UI如果不做横竖屏适配,基本会翻车。
我使用Flutter的LayoutBuilder做响应式适配:当宽度低于600dp时,设置页采用单列列表;高于600dp时,采用“左列菜单+右侧详情”的两栏布局。两栏布局的体验对触屏大屏更友好,能减少页面的跳转层级。在OpenHarmony的WindowStage配置里,允许App自适应方向。对于社区大屏设备,建议锁定为横屏,并且把系统状态栏隐藏掉,保证全屏沉浸。
有一点容易被忽略:在平板和大屏上,如果直接用手机的屏幕像素密度计算间距,视觉效果会显得非常空。我采用了一个“基准宽度”的缩放方法:在构建SettingItem时,根据当前屏幕宽度与480这段基准宽度的比值,动态调整内边距和间距。这个方法比直接用MediaQuery的scaleFactor更可控,因为在大分辨率下不会让字和控件比例失调。
6. 常见问题与排查技巧实录
6.1 Flutter环境与Gradle构建报错
结合网上的高频问题和自己团队的踩坑经历,整理一批在Flutter for OpenHarmony开发中最高频的报错和解决方案。
第一个高频问题:使用flutter create默认模板创建工程之后,直接执行flutter build hap,报错“You are applying Flutter's main Gradle plugin imperatively using the apply script method”。这个报错的意思是:OpenHarmony的Gradle插件不能用传统的apply方式加载,而要用插件DSL的plugins块显式声明。新建工程时,如果使用了老版本模板或者手动拷贝了Android工程的build.gradle,就会触发。解决办法是在build.gradle里把apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"改成plugins块的方式。
第二个高频问题:flutter: CMake Error at CMakeLists.txt:3 (project): generator Visual Studio 17 2022。这个经常出现在Windows环境下,因为Flutter在构建native插件时会调用CMake,如果系统里安装的CMake版本或者Visual Studio工具链不对,就会报这个错。在OpenHarmony开发时,推荐直接用DevEco Studio自带的CMake和Ninja工具链,不要用Visual Studio的CMake。具体做法是修改local.properties,指向DevEco内置的sdk cmake路径。还需要注意,插件里如果有native code,务必使用OpenHarmony SDK提供的ohos toolchain,而不是Android的NDK路径。
第三个高频问题:flutter热重载后浏览器没更新。这个问题看起来很蠢,但确实困扰了很多人。原因是Flutter在OpenHarmony上默认的debug模式是“JIT + hot reload”,但如果你启动的时候URL或设备选错了,或者启动的是release模式,热重载自然不会生效。另外,某些情况下OpenHarmony的Flutter分支在热重载后,Dart代码变更了,但原生侧的资源(如字体、图片)没有同步,需要手动执行flutter clean后重新构建。
6.2 系统设置页特有的错误
系统设置页有一个比较难排查的问题:在修改字体大小后,页面经常出现文字溢出(RenderFlex overflow)。原因很简单,自定义组件使用了固定高度。我在新版代码里做了很彻底的改造:所有SettingItem从Container改为Column+Expanded的组合结构,并给标题和描述添加了maxLines与overflow: TextOverflow.ellipsis。同时关键值字段(如当前亮度、字号数值)用Flexible包裹,保证在极端缩放比下不溢出。
另一个系统设置页的“暗坑”是Switch的滑块状态和实际配置不一致。老年用户经常快速切换开关,然后在设置保存前退出页面,导致UI显示状态与存储状态不同步。我的做法是:所有开关组件使用ValueNotifier作为状态管理,切换时立即触发持久化,不做“保存按钮”的中间态。这样虽然增加了一点写数据库的频率,但换来了状态的一致性,对体验和安全都是有帮助的。
6.3 权限与通知的细节坑
OpenHarmony的权限系统在API 9之后收得更紧了。如果targetSdkVersion设置得比较高,部分权限需要动态申请。这个在系统设置里做“权限管理”时容易出现的Bug是:用户拒绝了权限申请后,下次再进页面,点击开关没有任何反应——因为你的代码在权限被拒绝后没有走“引导用户去系统设置手动开启”的逻辑。
对于这个场景,我的实现是在权限被拒绝时,弹出一个Dialog,告诉用户“该功能需要摄像头权限,请在系统设置中开启”,并提供“前往设置”的按钮。按钮点击后,通过平台通道调用OpenHarmony的requestPermissionsFromUser,再次弹出生硬的系统弹窗。这里需要注意:不要死循环弹窗,如果用户已经选择了两次拒绝,就应该在引导页停住,把选择权留给用户。
通知的坑在“重复设置相同提醒”上。用户把服药提醒从9点改到10点,如果旧的提醒没有取消,就会在9点和10点各响一次。这段逻辑本身不难,难点在于Flutter层的提醒配置与OpenHarmony的NotificationManager之间要保持唯一的ID映射。我维护了一个map,key是提醒类型,value是notificationId。更新提醒时,先根据key取消旧id的提醒,再生成新的id。这个操作虽然简单,但能明显提升养老场景中的“可信赖感“。
7. 测试与质量保障的实践
7.1 单元测试与Widget测试
系统设置模块的业务逻辑相对独立,很适合做单元测试。比如字体大小的缩放计算、提醒时间的合法性校验、健康数据的单位换算,这些都可以用纯Dart测试覆盖。Flutter官方的flutter_test库在OpenHarmony工程里可以直接使用,不需要额外适配。
一个建议是给设置页的每个SettingItem做Widget测试:模拟点击、开关切换、验证文案是否正确展示。在OpenHarmony设备上跑Widget测试时会发现,某些跟平台通道相关的测试没法在单测环境执行,需要打上TestDefaultBinaryMessengerBinding的mock。我编写了一个松耦合的抽象层,把MethodChannel调用封装成abstract class,测试时直接注入fake实现。这样系统设置里的平台相关逻辑就可以用纯Dart进行单元测试了。
7.2 回归测试与多设备矩阵
养老App的上线很看重稳定性,每次改动都可能有“这个页面正常了,那个页面崩了”的回归风险。我在项目里搭了一套简单的自动化回归脚本:在关键页面(设置首页、编辑联系人、提醒配置)用Flutter的integration_test跑一遍核心流程的冒烟测试。Integration测试在OpenHarmony上执行的方式是flutter test integration_test,它会直接在设备上安装并运行。
设备矩阵方面,我的测试用机是RK3568开发板、一台7寸平板、一台10寸触屏一体机。三个设备的分辨率都不一样,能覆盖常见的布局适配问题。跑回归测试时,重点关注三个点:字体放大到最大后页面是否溢出、蓝牙设备断开后重新扫描是否卡死、通知到达后冷启动App是否能正确跳转到设置页。
7.3 性能监控与埋点
性能监控这块,我在系统设置关键操作(如保存配置、打开指定页面)都做了耗时埋点。埋点数据通过EventChannel上报给原生侧,批量写入本地文件,可定期上传。这些数据在前期版本优化中帮了大忙。举一个例子:从埋点数据里发现“联系人编辑页面”的平均停留时间超过了60秒,说明输入和保存的体验存在障碍,后来通过拉大输入框的触控区域,把操作成功率提升了将近三成。
帧率监控用Flutter自带的PerformanceOverlay即可在调试期观察,但线上版本不能开启。为了不影响性能,我有一个DIY方案:开启一个每秒只执行一次的Timer,读取SchedulerBinding.instance.currentFrameTimestamp,如果两帧间隔超过了100ms就记录一次卡顿。这个轻量监控放在系统设置这种低操作频率的页面,对性能影响几乎可以忽略。
8. 上线与维护阶段的经验和扩展思路
8.1 打包与签名
在OpenHarmony上打包HAP,和Android的APK有一点相似,但不完全一样。HAP的签名使用的是OpenHarmony的签名工具,签名文件是.p12和.cer。如果项目配置了CI/CD,需要把签名文件安全地存储到密钥管理服务中,不要在构建脚本里直接裸连。
系统设置模块在打包时还有一个需要注意的点:如果你在代码里引用了OpenHarmony的系统能力(比如通知、蓝牙、存储),需要在module.json5里声明对应的权限。漏掉权限声明最常见的表现是:在DevEco Studio里跑debug模式没问题,但打release包安装到设备上,功能就报错。因为debug模式可能自动授予了权限,而release模式是严格校验的。
8.2 远程配置与版本更新
养老设备通常没有用户主动去应用市场升级的习惯,所以App自身的版本更新能力很重要。我的做法是:在系统设置里增加“检查更新”入口,后台放在一个简单的静态配置服务器上,返回最新版本号、HAP包下载地址、强制更新标志。Flutter侧用dio下载新版本包,下载完成后通过平台通道调用OpenHarmony的安装接口。
这里踩过一个坑:用dio下载大文件时,如果网络中断,默认的restart逻辑会闪崩。我的实现是给dio的download方法增加了“断点续传+重试”的参数,同时用一个进度回调更新UI。实测在10MB级别的HAP包上,即使网络有小幅波动,也能稳定下载安装。
8.3 后续扩展思路
系统设置模块本质上是一个人机交互的“控制台”,完全可以在现有架构上不断扩展能力。比如增加“亲情守护模式”:子女通过云端修改老人设备上的设置项,远程调整字号、开关、更新联系人。再比如“一键诊断”功能:点击后自动检查网络、蓝牙、电池、存储状态,并把结果生成一张清晰的报告图,方便老人发给子女或客服,能大幅降低技术支持成本。
另外,我还在持续关注Flutter for OpenHarmony官方分支的更新速度。随着OpenHarmony生态的成熟,Flutter官方主线未来也有可能直接支持OpenHarmony,那时候插件兼容性会大幅改善。但从目前来看,这套基于fork分支+自建平台通道的架构依然是当前最稳妥的落地方式,而且它所带来的“底层能力抽象”思想,即使后续迁移到新SDK,性价比也颇高。
回到“系统设置”这个模块本身,我在实际开发中的体会是:适老化产品的设置页,不能只当做一个普通的列表页来做。每一个开关、每一处文案、每一次交互反馈,都应该站在老人的视角反复斟酌。技术上的坑可以靠经验趟平,但产品和人文层面的考量,才是这个模块真正的核心价值。这套项目做到后期,团队形成了一条不成文的规定——任何新功能的设置项,在提交代码前,都要先在“字体最大、对比度最高”的模式下点一遍。这个习惯,强烈建议所有做智慧养老或适老应用的团队都学习一下。
