最近在折腾一个智慧养老项目,核心功能是在 OpenHarmony 设备上用 Flutter 做一套心率监测 App。说实话,这个组合一开始并不被身边同事看好:Flutter 官方对 OpenHarmony 的支持还处在快速演进期,社区插件也不像 Android/iOS 那么全,光是环境搭建就要折腾好几天。但真把链路跑通之后,你会发现这套方案的上限其实很高——一套 Dart 代码可以同时覆盖 Android 和 OpenHarmony,UI 又能完全自由定制适老化的大字体高对比风格,这对健康类应用来说太合适了。
这篇文章就把我整个实战过程整理出来,从项目背景、技术选型,到 Flutter for OpenHarmony 环境搭建、BLE 心率数据采集、实时波形绘制、异常告警,以及最后在 RK3568 开发板真机上踩过的一堆坑,一次性讲清楚。适合正在调研鸿蒙生态、想把现有 Flutter 业务迁移到 OpenHarmony、或者想做跨端健康类 App 的开发者参考。文章里所有代码和步骤都是我实际跑通过的方案,你可以直接照着抄。
1. 项目背景与整体设计思路
1.1 这个 App 要解决的到底是什么问题
智慧养老这个概念喊了很多年,落到具体场景上,最刚需的功能其实就那几个:心率监测、跌倒检测、位置追踪、紧急呼叫。其中心率监测又是绝对的核心,因为心率是老人身体状态最直接的信号,心率过快、过慢、心律不齐往往意味着心血管隐患。
这个项目的起点是一家社区养老服务中心。他们要部署一批设备到老人家中和社区活动室,设备上跑一个 App,老人自己不用做任何复杂操作,设备会自动连接心率带,把实时心率显示在屏幕上,同时上传给护士站和家人。他们一开始设想的是 Android 平板方案,但后来考虑到设备采购渠道和后续国产化要求,就定了 OpenHarmony 系统作为目标平台。
我拿到需求后第一反应是:这不能按传统 App 的思路来做。养老场景的 App 有很强的特殊性——界面要极简、信息要大、操作要少、异常处理要自动。你不能让老人去点"连接设备""开始测量",这些都应该在启动后自动完成。用户只需要看得到当前心率数字、心跳有没有异常、电量还够不够,就够了。
1.2 为什么坚决选 Flutter 而不是原生 ArkTS
在 OpenHarmony 上做应用开发,最正统的选择是 ArkTS 加 ArkUI,毕竟是系统原生框架,性能好、权限控制最直接。但我最后还是选了 Flutter,原因有三个。
第一是代码复用。这个养老 App 大概率以后还要出 Android 版本和 iOS 版本(有一个版本是要放在家属手机上看数据的),如果用 ArkTS 写,等于给每个平台各写一套 UI,维护成本直接翻倍。Flutter 一套代码通吃,而且 UI 是自绘的,字体大小、颜色、布局在各端完全一致,这对适老化设计特别重要——老人眼里的界面必须是统一的,不能换个设备字号就变了。
第二个原因是Flutter 的 UI 表达力。心率监测要画实时波形图,ArkUI 也能画,但 Flutter 的 CustomPaint 配合 AnimationController 做这种动态绘制简直不要太顺手。而且 Flutter 生态里有大量现成的图表库、动画库,做波型图、健康报告这类页面效率高很多。
第三个原因更现实:社区适配已经能用了。OpenHarmony SIG 组织维护着 Flutter 的 ohos 分支,支持用 flutter build hap 直接产出 OpenHarmony 的安装包,我实测核心能力——Dart 虚拟机、平台通道、基础插件——都能跑通。说句公道话,现在 Flutter on OpenHarmony 还没到生产级成熟度,但已经过了"玩具阶段",做业务原型和内部试点完全够。
相比之下,React Native for OpenHarmony 社区也在推进,但目前第三方原生模块迁移难度大,生态完善度不如 Flutter。所以我的结论是:纯鸿蒙应用上原生 ArkTS,跨端应用上 Flutter,别犹豫。
1.3 心率监测方案选型:BLE 外接设备优先
心率数据从哪来,是这个项目最关键的决策点。我调研下来主要有三条路:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 光学 PPG 传感器直连 | 用 MAX30102 等模块采集手指/手腕透射光变化,算出心率 | 成本低,约几十元 | 需要写驱动,OpenHarmony 上适配麻烦;光学方案运动伪影大 |
| 蓝牙心率带 | 通过标准 BLE Heart Rate Service 读取心率 | 即插即用、协议标准、数据精准 | 需要外置设备,成本约百到几百元 |
| 手表手环 SDK 聚合 | 接华为/小米等手表 SDK 获取数据 | 数据连续、有厂商算法 | 绑定生态、SDK 适配 OpenHarmony 时间未知 |
我最终选了蓝牙心率带。最核心的原因是 OpenHarmony 系统自带 @ohos.bluetooth.ble 原生模块,BLE 通信链路可以直接打通,不需要碰底层驱动;而且心率带遵循蓝牙 SIG 的标准化 Heart Rate Service,协议是公开的,解析逻辑非常简单。至于成本,养老中心本来就是批量采购,一台设备配一条心率带,预算完全可接受。
如果后续要压低成本做家用版本,可以再评估 MAX30102 的 HDI 驱动方案,但那是另一个工作量级了,不是 App 层能解决的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与 OpenHarmony 移植要点
2.1 开发环境与版本匹配
Flutter for OpenHarmony 最大的坑是版本匹配。它不是官方主线分支,而是 OpenHarmony SIG 在维护,所以 Flutter SDK、OpenHarmony SDK、DevEco Studio 三者版本必须对得上,否则编译的时候会出现各种玄学错误。
我最终跑通的组合是这样的:
| 组件 | 版本 | 说明 |
|---|---|---|
| OpenHarmony | 4.0 Release(API 10) | 对应 SDK 10,RK3568/RK3588 开发板均可 |
| DevEco Studio | 4.0 Release | 用于构建 HAP 包和签名 |
| Flutter SDK | ohos-3.7 分支 | 从 OpenHarmony SIG 的 flutter_flutter 仓库拉取 |
| Node.js | 16+ | 构建 HAP 时需要 |
获取 Flutter SDK 的时候注意,千万别用官方 flutter.dev 的主线 SDK,那个不支持 ohos 平台。要到 OpenHarmony SIG 的 Gitee 仓库拉分支:
bash复制git clone -b ohos-3.7 https://gitee.com/openharmony-sig/flutter_flutter.git
export PATH=$PWD/flutter_flutter/bin:$PATH
flutter --version
然后配置好 DevEco Studio 的 SDK 路径,确保命令行能识别 OpenHarmony SDK。之后再创建工程:
bash复制flutter create --platforms=ohos,android --project-name health_app ./health_app
cd health_app
这里有个细节:--platforms 里除了 ohos,我建议同时带上 android。开发阶段大部分逻辑可以先在 Android 模拟器上调试,OpenHarmony 真机部署放在第二优先级,这样效率会高不少。
2.2 把 Flutter 工程跑上 RK3568/RK3588 开发板
我的靶机是 RK3568 开发板,8GB 内存版本,系统是官方 OpenHarmony 标准系统。这个板子做养老设备的原型机很合适,接口多、性能够用、成本也不高。
要在板上跑起来,最直接的方式是构建成调试模式的 HAP 包,用 hdc(OpenHarmony 的调试工具,类似 adb)安装:
bash复制flutter build hap --debug
hdc install build/ohos/.../entry-default-signed.hap
第一次跑的时候大概率会遇到签名问题,需要在 DevEco Studio 里配置自动签名。自动签名必须登录华为账号,这个环节很多人会卡住,因为 OpenHarmony 开发板的签名和 HarmonyOS 手机的签名流程不一样,稍微折腾。
如果只是想在桌面上调试 UI,其实还可以用 DevEco Studio 自带的模拟器(OpenHarmony 模拟器镜像),但模拟器对蓝牙这种硬件能力的模拟很弱,所以我建议,做 UI 层开发用 Android 模拟器,做蓝牙联调用真机,这个分工能帮你省下大量时间。
2.3 权限声明、连板调试与日志排查
OpenHarmony 对权限管理很严格,和 Android 一样需要在 module.json5 里声明权限。心率项目需要申请的权限有这几个:
json5复制{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.USE_BLUETOOTH" },
{ "name": "ohos.permission.DISCOVER_BLUETOOTH" },
{ "name": "ohos.permission.APPROXIMATELY_LOCATION" },
{ "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" }
]
}
}
注意 APPROXIMATELY_LOCATION——蓝牙扫描在 Android 上需要定位权限,OpenHarmony 同样有这个要求,不声明的话扫描不到任何设备。这个权限在运行时还要动态申请一次,代码里必须处理授权回调。
连板调试用 hdc:
bash复制hdc list targets
hdc shell
hdc file send local_path /data/local/tmp/
如果板子和电脑不在同一网段,可以先通过 USB 连接,然后执行 hdc tconn ip:port 切换到网络连接,方便后面边调试边看日志。日志过滤 Flutter 输出可以用:
bash复制hdc shell hilog | grep flutter
在 OpenHarmony 上调试 Flutter,我还是建议把 debugShowCheckedModeBanner: true 开着,这样能直观确认当前跑的是不是 Flutter 页面,避免到后面分不清哪部分是原生渲染哪部分是 Flutter 渲染。
3. 心率数据采集与波形绘制实现
3.1 从蓝牙拿到原始心率数据的完整链路
整个链路的架构是这样的:
code复制心率带 → BLE 广播/连接 → OpenHarmony 蓝牙协议栈
→ ArkTS 原生侧通过 @ohos.bluetooth.ble 读取特征值
→ MethodChannel 传给 Flutter
→ Dart 解析心率数据 → 更新 UI / 存储 / 告警判断
底层硬件通信必须在 OpenHarmony 原生侧(ArkTS 模块)完成,因为 Flutter 侧的蓝牙插件目前对 OpenHarmony 的适配还不完善。最开始我尝试直接用 flutter_blue_plus 去连接,结果发现它内部依赖的蓝牙抽象层在 ohos 上没实现,属于"挂着兼容层但实际跑不通"的状态。后来我干脆走平台通道自封装,反而简单可控。
ArkTS 侧的核心代码示意如下(不同 API 版本签名可能略有差异,但我尽量写通用版本):
typescript复制import ble from '@ohos.bluetooth.ble';
import { BusinessError } from '@ohos.base';
let gattClient: ble.GattClientDevice | undefined;
export function startScan() {
ble.startBLEScan({
filters: [{ name: 'HeartRateBand' }],
interval: 300,
matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE
});
}
ble.on('BLEDeviceFind', (devices: Array<ble.ScanResult>) => {
// 找到目标设备后停止扫描并连接
ble.stopBLEScan();
ble.connect(devices[0].deviceId).then((client) => {
gattClient = client;
// 发现服务后可遍历 services,找到 0x180D
});
});
查找心率和体力服务(Heart Rate Service)的标准 UUID 是 0000180D-0000-1000-8000-00805F9B34FB,其中的心率测量特征值(Heart Rate Measurement)UUID 是 00002A37-0000-1000-8000-00805F9B34FB。我们要做的就是订阅这个特征值的通知,心率带每次测量出心率就会主动推送过来。
找到特征值后,开启通知:
typescript复制client.setCharacteristicChangeNotification({
serviceUuid: '0000180D-0000-1000-8000-00805F9B34FB',
characteristicUuid: '00002A37-0000-1000-8000-00805F9B34FB',
enable: true
});
client.on('BLECharacteristicChange', (key, value) => {
// value是ArrayBuffer,通过MethodChannel回调给Flutter
});
在 Dart 侧定义 MethodChannel:
dart复制class HeartRateChannel {
static const _channel = MethodChannel('health_app/ble');
static Future<void> init() async {
_channel.setMethodCallHandler((call) async {
if (call.method == 'onHeartRateData') {
final bytes = (call.arguments as Uint8List);
final hr = parseHeartRate(bytes);
HeartRateStore.instance.add(hr);
}
});
}
}
这套链路的关键点在于:原生侧只是数据的搬运工,所有业务逻辑都放 Dart 侧。这样以后如果要迁移到 Android 平台,只需要把原生侧换成 Android 的 BLE 实现,Dart 侧一行不用改。
3.2 先听懂协议再写解析:Heart Rate Measurement 格式详解
拿到特征值推送的原始数据后,不能直接把它当成心率值。Heart Rate Measurement 特征值的第一个字节是 Flags 标志位,后面的内容取决于标志位,格式是固定的。我一开始没仔细读协议,直接把第一个字节当心率,结果显示 40、80、160 的乱跳,排查了半天才发现问题。
协议格式长这样:
- 第 1 字节(Flags)的 bit0 表示心率值格式:0 表示 UINT8,1 表示 UINT16
- bit3 表示是否存在能量消耗字段
- bit4 表示是否存在 RR 间期字段
- 之后跟心率值(根据 flag 决定 1 还是 2 字节)
- 如果有 RR 间期,每两个字节表示一个 RR 间期,单位是 1/1024 秒
所以解析代码如下:
dart复制HeartRateData parseHeartRate(Uint8List payload) {
if (payload.isEmpty) {
return HeartRateData(heartRate: 0);
}
int offset = 0;
final int flags = payload[offset++];
final bool isUint16 = (flags & 0x01) != 0;
int heartRate;
if (isUint16 && payload.length >= offset + 2) {
heartRate = payload[offset] | (payload[offset + 1] << 8);
offset += 2;
} else if (payload.length > offset) {
heartRate = payload[offset];
offset += 1;
} else {
return HeartRateData(heartRate: 0);
}
int? rrIntervalMs;
if ((flags & 0x10) != 0 && payload.length >= offset + 2) {
final int raw = payload[offset] | (payload[offset + 1] << 8);
rrIntervalMs = (raw / 1024).round();
}
return HeartRateData(heartRate: heartRate, rrIntervalMs: rrIntervalMs);
}
顺带说一句,RR 间期是两次心跳之间的时间间隔,单位 1/1024 秒。这个数据对心率变异性分析贼有用,老年人心脏健康评估里 HRV 是一个重要指标。虽然 MVP 版本没用上,但我在数据结构里先存下来了,后面要做 HRV 分析就是顺手的事。
3.3 虚假值过滤与实时波形绘制
心率带数据虽然比光学方案稳定,但依然会有丢包、瞬时跳变的情况,尤其是老人活动、传感器贴合松动的时候。直接拿原始值上屏,画出来的波形会像心电图里的偶发早搏一样突然抽一下,家属看到要被吓坏的。
所以我在 Dart 侧做了一层简单但有效的滤波:剔除异常值 + 滑动平均。
dart复制class HeartRateFilter {
final _window = <int>[];
static const _maxWindow = 5;
static const _minValid = 30;
static const _maxValid = 200;
int push(int raw) {
// 先剔除明显不合理的心率值
if (raw < _minValid || raw > _maxValid) return _lastValid;
_window.add(raw);
if (_window.length > _maxWindow) {
_window.removeAt(0);
}
final avg = _window.reduce((a, b) => a + b) ~/ _window.length;
_lastValid = avg;
return avg;
}
}
这里说个实践经验:滤波的窗口不是越大越好。窗口大了,心率波形会变得非常"钝",心率发生真实剧烈变化时反应不过来,比如老人突然心动过速,界面要好几秒才更新,这可能在医疗场景下误事。我测试下来 5 个点窗口加异常值剔除是最平衡的组合。
波形绘制用 Flutter 的 CustomPaint 就能搞定。核心思路是维护一个固定长度的数据队列,每次有新数据就入队、移除最旧的数据,然后触发重绘,画笔从左往右画一条折线,看起来就像实时的心率波形在滚动:
dart复制class WaveformPainter extends CustomPainter {
final List<double> values;
final double minValue;
final double maxValue;
WaveformPainter({required this.values, required this.minValue, required this.maxValue});
@override
void paint(Canvas canvas, Size size) {
if (values.length < 2) return;
final paint = Paint()
..color = const Color(0xFF23A06B)
..strokeWidth = 2.5
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
final path = Path();
for (int i = 0; i < values.length; i++) {
final dx = i / (values.length - 1) * size.width;
final normalized = (values[i] - minValue) / (maxValue - minValue);
final dy = size.height - normalized * size.height;
if (i == 0) {
path.moveTo(dx, dy);
} else {
path.lineTo(dx, dy);
}
}
canvas.drawPath(path, paint);
}
}
还有一个细节,UI 刷新频率不需要 60fps。心率带每秒推送约 1 次数据,即使全是最快的心率数据,实际刷新也就每秒几次。如果每帧都重绘会让 GPU 空转发热,对养老设备这类长期开机运行的场景不友好。我实际控制重绘频率在 10Hz 以内,肉眼看起来依然流畅,板子表面温度也正常。
4. 养老场景的功能落地:告警、存储与适老化
4.1 心率异常检测与告警
实时显示心率只是最基础的功能,养老场景真正有价值的是异常告警。这个功能做得准不准,直接决定设备能不能用。
我参考了《中国老年人心血管疾病风险评估与管理专家共识》里对静息心率的分层:成年人正常静息心率 60~100 次/分,但 70 岁以上老人的正常范围要放宽到 55~90 次/分。心动过速的启动值一般是 100,心动过缓一般在 50 以下。在实现里,我把低阈值设成 50、高阈值设成 120,这样能在尽早发现问题的同时避免过度敏感。
单纯"超过阈值就告警"会误报不断。老年人有一次憋气、翻身、心率带贴合不好,都可能产生瞬间的异常值。我加了一个持续时长判定机制:
dart复制Timer.periodic(const Duration(seconds: 3), (timer) {
final now = DateTime.now();
final recent = samples
.where((s) => now.difference(s.time).inSeconds < 15)
.toList();
if (recent.length < 5) return;
final avg = recent.map((e) => e.value).reduce((a, b) => a + b) / recent.length;
if (avg < 50 || avg > 120) {
if (_alerting) return; // 已处于告警状态,不重复触发
_triggerAlert(avg);
} else {
_alerting = false;
}
});
具体规则是:取最近 15 秒内至少 5 个数据点,如果平均值仍然越界,才触发告警。告警内容通过本地通知推送给老人本人,同时可选通过 HTTP 上报给家属端。这个机制跑下来,误报率明显下降,真实异常也基本都能捕捉到。
4.2 历史数据存储与上报
心率数据不能只在屏幕上闪一下,必须留存下来,为后面的医生评估和趋势分析做准备。
OpenHarmony 上做本地存储,可选择 @ohos.data.preferences 或 @ohos.data.relationalStore(关系型数据库)。但既然是 Flutter 应用,我优先用 Flutter 生态的 sqflite。实测 sqflite 在 ohos 上有兼容方案,存储心率记录完全没问题。如果不依赖数据库插件,也可以用最朴素的方式——把数据写成 CSV 或 JSON 文件存到本地:
dart复制class HeartRateRecorder {
static Future<void> append(int heartRate) async {
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/heart_rate.csv');
final line = '${DateTime.now().toIso8601String()},$heartRate\n';
await file.writeAsString(line, mode: FileMode.append);
}
}
CSV 格式好处是直接能用 Excel 打开,护工人员拿去给医生看,不需要任何额外工具。如果要给家属远程看,则需要一个轻量后端,上报接口很简单:
dart复制Future<void> uploadBatch(List<HeartRateRecord> records) async {
final client = http.Client();
final response = await client.post(
Uri.parse('https://your-api.example.com/upload'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(records.map((e) => e.toJson()).toList()),
);
}
这里有一个容易被忽略的点:健康数据属于高敏感隐私数据。上报接口必须走 HTTPS,数据库和日志里我都没有记录手环设备的蓝牙 MAC 地址,只存内部生成的用户 ID,避免硬件标识和设备主人绑定。这是做健康类 App 的基本底线。
4.3 适老化交互设计的几个容易被低估的细节
适老化这个事,外面讲得很多,但真正落到代码里,我发现有几个细节是"外人不知道,做过才知道"的。
第一是字体不是越大越好。我把主心率数字做到了 96 号字,但详情标签只用了 32 号字。如果全部放大,重要信息和次要信息就没有层次了,老人反而不知道该看哪里。
第二是高对比度但不是纯黑纯白。用纯白背景加纯黑字,在老花眼和老化的显示器上会有很强的眩光。我实测下来,#F5F7FA 的背景加上 #1F2329 的文字,对比度足够高又不刺眼。异常状态的红色用 #D93025,这个颜色的明度和饱和度在绝大多数屏幕上都清晰可辨。
第三是触控防误触。老人手部的精细动作能力下降,容易误碰。界面上所有可操作区域都至少 64dp 高,列表项不支持滑动删除(防止误删),侧滑返回和底部手势导航在 App 页面里尽量禁用,避免老人在无意间退出程序。这些虽然只是设置上的调整,但真正常用的养老 App 都会注意到这些。
5. 常见问题与排查技巧实录
我在这个项目里踩的坑,比写代码的时间还多。整理几个最典型的,给后面做 Flutter on OpenHarmony 的同学提个醒。
5.1 编译期问题:Flutter 插件加载失败
热词里那个报错 Flutter error resolving plugin [id: 'dev.flutter.flutter-plugin-loader', version ...] 我遇到不下十次。网上搜到的解决方案大多是针对 Android 工程的,对 ohos 平台不完全适用。
我最后的解决思路是:先确认 Flutter SDK 分支和 pubspec.yaml 里声明的插件是否支持 ohos 平台。很多 Flutter 第三方插件没有适配 OpenHarmony,编译时 flutter-plugin-loader 解析不到对应平台实现就会报这个错。解决办法是查插件仓库是否提供 ohos 目录;如果没有,就只能用平台通道自己封装原生实现,或者把插件换成支持 ohos 的替代品。
另外还有一个常见问题:在 settings.gradle 或 build.gradle 中用了 apply 方式引用 Flutter 插件,也会和 OpenHarmony 的构建系统冲突。建议直接用新版推荐的方式,让构建系统自动识别。
5.2 连接期问题:扫描不到设备或 GATT 连接失败
扫描不到心率带,九成是权限没申请或位置开关没开。前面说过,OpenHarmony 的蓝牙扫描依赖定位权限,如果 module.json5 里没有,或者运行时没动态申请,startBLEScan 会静默失败,控制台也没有明显报错。我当时排查了很久,最后是在权限面板里看到位置权限没弹窗,才反应过来。
GATT 连接失败最常见的报错是 Error code: 133,对应 GATT 连接被拒绝。这种一般是设备端已经绑定了其他连接(比如手机上的厂商 App),或者心率带进入了低功耗休眠状态。解决办法是先重启心率带,手机端把蓝牙缓存清一下,再试。在开发阶段,我甚至会把心率带的"解除绑定"操作也写在调试页面上,方便反复测试。
5.3 数据质量问题:心率值跳变、数据断流
心率值跳变的问题前面提过,用滤波解决。但还有一种情况容易被忽略:心率带贴得太松。蓝牙协议层解析不到错误,但数据本身就不准。这个没法在代码里完全修复,只能通过 UI 提示用户"请贴合心率带"。我在界面上做了一个信号质量指示,如果连续多次解析出异常波形的 RR 间期异常,就提示佩戴有问题。
数据断流更常见的原因是系统后台把 App 挂起了。OpenHarmony 对后台任务有严格限制,App 退到后台后蓝牙回调频率会大幅下降甚至完全停止。这就要用到长时任务能力,声明 ohos.permission.KEEP_BACKGROUND_RUNNING 并申请 continuousTask,把应用标记为前台服务,才能保证后台持续接收数据。如果设备是养老中心专用机,最好把它设为开机自启动 + 始终前台运行,这是最稳妥的做法。
5.4 性能与耗电优化
RK3568 开发板性能比一般手机弱,跑 Flutter 渲染频率高了会明显发热。我做了三个优化:
一是降低不必要的动画刷新率,心率波形固定用 10Hz 重绘,不用 60Hz;二是用 StreamBuilder 或 Provider 的局部刷新,不要让通知栏时间、电池电量这些次要信息跟着心率数据一起刷新;三是要特别留意日志输出——Dart 侧频繁 print 会在 debug 模式下拖慢速度,Release 包就不会有这个问题。
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 插件加载失败 | 编译报 flutter-plugin-loader 解析错误 | 检查插件是否支持 ohos,不支持则自封装原生通道 |
| 扫描不到设备 | 无报错但列表为空 | 确认定位权限已动态申请,系统位置开关已打开 |
| GATT 连接失败 | 报 133 错误 | 重启心率带,清理蓝牙缓存,解除旧绑定 |
| 后台数据断流 | 息屏后心率停止更新 | 申请长时任务权限,使用 continuousTask 前置服务 |
| 心率值跳变 | 显示 200+ 或 0 | 异常值剔除 + 5 点滑动平均过滤 |
| 界面卡顿发热 | 发热明显、帧率低 | 控制重绘频率在 10Hz 内,做局部刷新 |
6. 一些实操心得和后续扩展方向
做完这个项目,我最大的感触是:Flutter on OpenHarmony 已经具备做真实业务的能力了,但你要把它当作一个"新平台"来对待,而不是 Flutter 的"又一个编译目标"。蓝牙、定位、后台任务这些系统能力没有现成的 Flutter 插件可用,需要自己写原生通道,这对团队的要求不低。
如果再让我选一次,我依然会选 Flutter,但我会在项目启动的第一天就规划好"原生能力抽象层"——把蓝牙、通知、定位这些能力统一封装成 Dart 接口,底层到底是 ohos 还是 android 实现,由各自平台去适配。这样就算以后要把整块业务迁移到其他平台,也只是写一个新适配层的事,业务代码完全不动。
后续扩展方面,我打算在 v2 版本里加三个东西:一是跌倒检测,通过加速度计数据结合心率变化综合判断;二是睡眠监测,把心率带整夜的数据汇总生成睡眠报告;三是语音播报,心率异常时直接通过设备喇叭提醒老人"请坐下休息"或"请联系家属"。如果设备需要联网上报,服务端用轻量级 MQTT 比 HTTP 轮询更适合这种常连接场景,OpenHarmony 社区里也有人把 Mongoose 这类嵌入式网络库移植过来,维护长连接更方便。
最后分享一个调优小技巧:心率波形图的历史数据别只存在内存里,建议同时落一份到本地文件。有一次我调试界面样式,反复重启应用,结果之前的内存数据全丢了,一整天的心率曲线都看不到,白白浪费了用户的测试数据。从那以后我就养成了边采集边写文件的习惯,这算是一个很实用的小教训。
现阶段如果你也准备在 OpenHarmony 设备上做健康管理类应用,我的建议是:硬件选型第一,先确认心率带支持标准 BLE Heart Rate Service;软件架构第二,把平台通道抽象好,做好版本锁定的记录;最后再考虑界面和交互。把这三件事想清楚,整个项目的坑会少一半。
