1. 项目背景与需求拆解:为什么交通服务是智慧养老App里最硬的骨头
接触 Flutter for OpenHarmony 的智慧养老App项目时,我本来以为最大的难点在"跨端适配"或者"鸿蒙生态不成熟"上。真正动手以后才发现,交通服务模块才是整个App里最考验工程能力的一块。挂号、缴费、健康档案这些功能都是App内闭环,用户点几下就能完成,但交通服务完全不同——它涉及真实世界的位置、时间、网络波动、天气变化,还涉及老人面对突发状况时的应对能力。
先说一个我在需求调研阶段印象很深的场景。有位社区负责人告诉我,他们试过给老人装网约车App,结果发现老人连"确认上车点"这个步骤都容易误触,更别提在高峰期手动输入目的地。老人经常把"儿子家"说成"那个路口往里走的地方",这种口语化地址根本没法直接变成地图坐标。所以交通模块的功能设计,不是把高德或百度地图抄一遍,而是要把出行链条拆碎,重新按老人的认知习惯组装。
我当时把需求拆成了五个优先层级,这是和产品、社区运营反复碰过的结果:
- 一键出发:首页放常用地点大卡片,点一下直接进入路线规划,不需要手动输入起点终点。
- 实时公交:能看附近站点、线路、下一班还有几分钟,比等导航路线优先级更高。
- 大字版路线指引:明确告诉老人"坐几路、在哪站下、大概走多少米",而不是甩一张复杂路线图。
- 亲情守护:子女可以远程查看老人实时位置,设定电子围栏,老人走出范围自动通知。
- 语音播报:关键节点用语音提醒,弥补老人看不清屏幕的问题。
为什么选 Flutter for OpenHarmony 这条路,而不是纯OpenHarmony原生开发?原因有三。第一,养老类项目往往不是只为一个系统做的,Android设备、鸿蒙设备、未来可能的iOS设备都要覆盖,Flutter一套代码能省掉很大一部分维护成本。第二,适老化UI需要高度定制,Flutter的自绘渲染机制可以保证同一套视觉逻辑在所有平台上完全一致,不依赖系统控件风格。第三,OpenHarmony生态还在成长期,Flutter社区的插件和工具链相对更成熟一些,遇到问题能参考的资料更多。
当然,这条路并不好走。OpenHarmony上的Flutter SDK不是官方主分支维护的,而是OpenHarmony SIG 团队在维护,版本更新节奏和Flutter官方不完全同步,很多Android上随手可用的插件在OpenHarmony上需要自己补平台通道。这些坑我在后面会详细展开。
1.1 老年用户出行的真实痛点清单
做适老化App最忌讳的一点,就是坐在办公室里凭空想象老人"应该需要什么"。我把调研中收集到的真实痛点整理成了一张清单,这张清单直接决定了我后面功能模块的优先级:
| 痛点 | 具体表现 | 对应功能设计 |
|---|---|---|
| 不会用网约车 | 不知道在哪叫车、怕付错钱、看不清司机信息 | 一键呼叫+子女代叫,行程信息自动推送给子女 |
| 公交信息看不懂 | 站牌字小、不知道车还有多久到 | 附近站点自动识别、到站时间大字显示 |
| 目的地表达模糊 | 说不清地址,只能用"老张家""菜市场那边" | 常用地点预存,子女远程维护地址库 |
| 容易走失 | 老人对陌生区域方向感差 | 电子围栏、行程共享、异常停留提醒 |
| 误触率高 | 手指灵活度下降,容易点错 | 大点击区域、二次确认、操作可撤销 |
这张表做出来之后,整个交通服务模块的开发方向就清楚了:不是做一个完整的导航App,而是做一个"帮老人安全出门"的轻量工具。功能可以少,但每个功能必须可靠、可感知、可兜底。
1.2 功能边界与模块划分
基于上面的痛点清单,我把交通服务模块划分成了四个子模块:
- 公交服务:附近站点查询、线路到站时间、收藏线路。
- 路线规划:一键到家/一键去医院、公交/步行混合路线、步骤列表式大字展示。
- 安全守护:实时位置共享、电子围栏、异常提醒。
- 语音辅助:操作引导语音、路线播报、来电报读(这个后来因为权限问题砍掉了,原因后面说)。
每个子模块内部再细分状态机,比如公交查询要处理"正在刷新""无网络""无数据""加载成功"四种状态。这些状态不是随手写的,而是我在测试时发现老人经常会对着一个一直转圈的按钮干等,他们不会主动去下拉刷新,也不会返回重进。所以所有异步操作都必须有明确的超时机制和自动重试策略,这是适老化App和普通App一个很本质的区别。
1.3 为什么现在就要上OpenHarmony
有的人可能会问,OpenHarmony生态还不成熟,为什么要在这个时间点做投入?我的看法是,智慧养老是一个典型的B端带动C端的场景,社区、养老院、政府项目采购设备时,成本和安全可控性往往比生态丰富度更优先。OpenHarmony设备在价格和可控性上有明显优势,而且这类项目一旦落地就是持续好几年的运维关系,现在进场积累技术经验,恰恰能吃到窗口期红利。
Flutter for OpenHarmony 正好是这两者的结合点。Flutter负责跨端一致性和开发效率,OpenHarmony负责提供国产可控的硬件底座。我在选型时也对比过 React Native for OpenHarmony,但仔细看下来,Flutter的渲染引擎是自绘的,不依赖原生控件,在OpenHarmony这种控件体系和Android有差异的系统上,一致性的保障更彻底。后面的事实也证明这个判断基本正确——至少在UI层,我几乎没有为OpenHarmony单独写过样式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与工程初始化:先踩平编译期的拦路虎
这一节我专门讲环境搭建,因为很多人在 OpenHarmony 上跑 Flutter 项目,第一步就被环境劝退了。我自己也在这上面折腾了两天,把报错一个个记录下来,这里直接给结论。
2.1 Flutter for OpenHarmony SDK 的获取与版本对应
OpenHarmony 的 Flutter SDK 不在 Flutter 官方仓库,而在 OpenHarmony SIG 维护的 flutter_flutter 仓库。安装时最容易犯的错,就是直接去 flutter 官网下标准版 Flutter SDK,然后照着鸿蒙的文档跑——这样在 flutter doctor 阶段就过不去,因为标准 SDK 根本不认识 ohos 平台。
我用的版本组合给你参考:
| 组件 | 版本 | 说明 |
|---|---|---|
| OpenHarmony | 4.0 Release及以上 | 太低版本对 Flutter 支持不完整 |
| flutter_flutter | 3.7.12-ohos 分支 | 对应 Flutter 3.7,目前最稳 |
| DevEco Studio | 4.0 及以上 | 用于编译和签名 OpenHarmony 工程 |
| hdc | 随 SDK 附带 | OpenHarmony 的调试桥,类似 adb |
配置完环境变量后,记得检查一下 flutter doctor -v 能否识别 ohos 工具链。我一开始没装 DevEco Studio 的 command line tools,结果 flutter doctor 就一直报找不到 SDK,后来才发现 ohos 的 SDK 路径需要单独配置到 LOCAL_PROPERTIES 里。
2.2 创建工程:flutter create 的坑
OpenHarmony 的 Flutter 工程和 Android 工程结构上有差异。用 flutter create --platforms ohos 创建项目时,会产生一个 ohos 目录,结构类似 Android 的 android 目录,但里面是 DevEco Studio 的工程格式。
这里有一个非常容易踩的坑:如果你用 flutter create 默认参数创建工程,它只生成 android、ios、web 目录,没有 ohos。必须使用 --platforms ohos 参数。而且,如果你是在已有 Flutter 工程里加 OpenHarmony 支持,不能直接手动创建 ohos 目录,需要用 OpenHarmony SIG 提供的 flutter create --platforms ohos . 命令补全工程结构,否则很多配置文件对不上。
2.3 hdc 查看系统版本:确认设备状态的标准动作
在 OpenHarmony 设备的日常调试中,hdc 命令是绕不开的。hdc 相当于 Android 世界的 adb,但它是 OpenHarmony 自己的调试工具。连接开发板或真机后,我做的第一件事永远是确认系统版本和设备型号,避免 Flutter 运行时和系统不兼容。
查看系统版本的标准命令:
bash复制hdc shell param get const.product.name
hdc shell param get const.product.version
第一条命令输出设备名称,第二条输出系统版本号。这两个信息能帮我快速判断当前设备的 OpenHarmony 版本是否满足 Flutter SDK 要求。如果版本过低,Flutter 应用在安装阶段就会直接报错,或者运行时出现渲染异常。
还有两个高频命令也一并给你:
bash复制hdc list targets # 查看已连接设备
hdc file send local remote # 推送文件到设备
如果 hdc list targets 看不到设备,先排查 USB 调试模式是否开启,再看驱动。这个和 adb 的使用体验几乎一样。
2.4 编译期报错:"flutter's main gradle plugin imperatively" 怎么破
你在 OpenHarmony Flutter 社区经常能看到这个报错:You are applying Flutter's main Gradle plugin imperatively using the apply script method, which is not supported. 这个报错的本质是:新版 Flutter Gradle 插件要求使用 plugins DSL 方式声明插件,而旧工程或某些模板还在用 apply 命令加载。
出现这个问题的场景通常是:你在社区下载了一个老的 Flutter 工程,然后在 OpenHarmony 环境下尝试编译,或者你的项目里某些依赖库还在使用老式 Gradle 写法。
解决办法有两个方向:
- 升级工程配置:在
android/settings.gradle中,把老式的apply from: "$flutterRoot/packages/flutter_tools/gradle/app.gradle"改成plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" }的方式。 - 降级 Flutter 版本:如果工程代码太老,升级成本高,可以直接切到对应的老版本 flutter_flutter 分支。
我在项目里遇到的是另一个变种:flutter error resolving plugin [id: 'dev.flutter.flutter-plugin-loader', ver...]。这个报错通常是 Gradle 无法解析插件仓库,需要检查 settings.gradle 里是否配置了 google()、mavenCentral() 以及 OpenHarmony 的插件仓库地址。同时,pluginManagement 块里的 repositories 要保证网络可访问。
这种 Gradle 类问题占了 OpenHarmony Flutter 开发环境问题的一半以上,我的建议是:尽量用新版 flutter_flutter 分支 + 新模板生成的工程,不要在老旧工程上反复挣扎,时间成本不划算。
2.5 编译产物清理:哪些可以放心删
OpenHarmony Flutter 工程的编译产物比 Android 工程更占空间,尤其是反复编译几次之后,磁盘可能被撑爆。我之前在 RK3568 开发板项目上就遇到过磁盘满了导致编译失败。
可以放心删的目录:
bash复制build/ # Flutter 编译产物
ohos/.cxx/ # C++ 编译缓存
ohos/build/ # OpenHarmony 工程编译产物
ohos/.gradle/ # Gradle 缓存
.gradle/ # 全局 Gradle 缓存
清理命令:
bash复制flutter clean
如果执行 flutter clean 后还觉得不干净,可以手动把下面这些目录也删掉,然后重新执行 flutter pub get:
bash复制rm -rf build ohos/.cxx ohos/build ohos/.gradle .gradle .dart_tool
注意:.dart_tool 删除后需要重新 flutter pub get,否则 IDE 会找不到插件。这个操作在换分支或切换依赖版本后尤其有用。
3. 交通服务的技术选型:地图、定位和公交数据从哪来
环境跑通以后,真正的业务挑战才刚开始。交通服务依赖三块基础能力:定位、地图、路线/公交数据。这三块在 Android 上都有现成 SDK,但在 OpenHarmony 上,每一块都需要重新审视。
3.1 定位方案:MethodChannel 桥接原生位置服务
OpenHarmony 有自己的位置服务接口,能力上和 Android 的 LocationManager 类似。但在 Flutter for OpenHarmony 上,并没有一个现成的 geolocator 插件能直接跨平台工作。我当时调研了十几个插件,结论是:不能直接依赖社区插件,必须自己用 MethodChannel 桥接。
实现逻辑是这样的:
- 原生侧(OpenHarmony ArkTS 或 C++ 编写)通过系统定位接口获取经纬度。
- Flutter 侧通过 MethodChannel 调用原生方法,异步拿到位置结果。
- 定位结果统一封装成自定义 Dart 对象,避免上层业务依赖具体平台。
核心的 Dart 侧代码大致长这样:
dart复制class LocationService {
static const _channel = MethodChannel('com.example.app/location');
static Future<LocationResult> getCurrentLocation() async {
try {
final result = await _channel.invokeMethod('getCurrentLocation');
return LocationResult.fromMap(result);
} on PlatformException catch (e) {
throw LocationException(e.message ?? '定位失败');
}
}
}
需要提醒的是,OpenHarmony 的定位权限和 Android 不太一样。除了在 module.json5 里声明权限外,还需要在应用运行时动态申请。而且不同版本的系统对后台定位的限制也各不相同,如果只是交通服务,建议只申请前台定位权限,后台定位放到后面亲情守护模块再说。
3.2 地图方案:没有完美方案,先用自绘地图跑通
地图是整个交通服务里最让人头疼的部分。OpenHarmony 上目前没有像高德、百度那样功能完整的地图 SDK 适配,直接接入客户端地图 SDK 的路暂时走不通。
我评估过几套方案:
| 方案 | 优点 | 缺点 | 结论 |
|---|---|---|---|
| 接入高德/百度客户端SDK | 功能完整 | OpenHarmony 未适配,无法使用 | 暂缓 |
| 华为 Map Kit | 与鸿蒙生态亲和 | OpenHarmony 开源版本上支持不完整 | 有条件时接入 |
| 自绘地图(flutter_map + 瓦片) | 跨平台一致、可控 | 功能有限、无路况 | 当前最优 |
| 静态地图图片 + 路线列表 | 实现最快、流量消耗低 | 交互弱 | 兜底方案 |
最后我选的方向是:自绘地图用于展示概览,核心导航信息用大字版列表呈现。为什么这样组合?因为对于老年用户,地图只是辅助理解"我在哪"和"要往哪走",真正决定体验的是"坐几路车、在哪站下"这种明确指令。与其在地图交互上费很大劲,不如把路线步骤做好、做清楚,这反而是这个项目最成功的一个选择。
自绘地图采用 flutter_map + OpenStreetMap 瓦片。需要注意,瓦片图源在国内访问速度不稳定,所以我在客户端做了瓦片缓存,用户第一次浏览过的区域,之后再次打开时直接从本地加载,体验会好很多。
3.3 公交与路线数据:服务端中转是唯一正确姿势
公交线路、到站时间、路线规划这些数据,OpenHarmony 客户端直接对接高德 Web 服务 API 也能做,但有两个问题:一是开发者 Key 暴露在客户端里,随时可能被盗用;二是响应数据需要二次加工才能变成适老化展示格式,放在客户端里处理逻辑会非常臃肿。
所以我在架构上做了一个简单的服务端中转层:
text复制Flutter App -> 自己的后端服务 -> 高德 Web服务API -> 返回结构化数据 -> Flutter App
这样做的好处非常明显:
- 安全性:高德 Key 保存在服务端,永不暴露。
- 灵活性:可以在服务端把高德的原始响应转换为 App 需要的"适老化模型"。
- 可扩展性:以后接入更多数据源时,客户端代码完全不用改。
服务端返回的数据结构,我是这样设计的:
json复制{
"origin": "社区养老中心",
"destination": "市人民医院",
"steps": [
{
"index": 1,
"type": "walk",
"instruction": "步行120米到社区养老中心站",
"distance": 120
},
{
"index": 2,
"type": "bus",
"instruction": "乘坐27路公交车,坐5站到市医院站下车",
"line": "27路",
"stopCount": 5
},
{
"index": 3,
"type": "walk",
"instruction": "下车后步行80米到达市人民医院",
"distance": 80
}
],
"estimatedMinutes": 35
}
这个结构是我专门为适老化展示设计的。每一步都是完整的一句话,而不是像高德那样拆成"上车""下车""步行"等碎片信息。老人只需要跟着步骤一条条看,不会漏掉关键信息。
3.4 路线规划的请求与响应流程
具体请求流程是这样的:
- 用户点击"一键到医院"。
- 客户端启动定位,拿到当前经纬度。
- 客户端把"当前坐标 + 目的地名称(预存在常用地点库里)"发给后端。
- 后端调用高德路线规划 API,拿到原始路线数据。
- 后端对原始数据做降噪和重组:只保留步行、公交两类步骤,过滤掉对老人无意义的信息。
- 后端返回上面的自定义 JSON,客户端直接渲染。
这个流程里,最容易被忽略的一点是坐标纠偏。OpenHarmony 设备返回的经纬度坐标系和高德使用的坐标系不一致,如果不在服务端做坐标转换,路线规划的起终点会偏出几百米。我在联调时就遇到过,定位点显示在马路中间,后来加了坐标系转换函数才解决。这个细节建议你提前处理。
4. 核心功能实现:公交查询、路线展示与适老化交互
架构定了以后,功能实现就变成纯 Flutter 开发了。这一节我把四个核心功能的实现细节和适老化交互逐一说明。
4.1 首页设计:一键出发,不给老人选择困难症
首页是整个App的门面,也是交通服务模块的入口。我的设计原则是:一屏之内,必须完成操作。首页顶部是一个大大的地址卡片,显示常用地点,比如"市人民医院""菜市场""儿子家",每个地点一张大卡片,卡片高度不小于 80dp,防止误触。下面是"公交查询"按钮和"行程分享"按钮。
现在车辆位置:分享给你一段我实现首页卡片时的核心代码逻辑:
dart复制class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
_buildGreeting(),
Expanded(
child: ListView.builder(
itemCount: favoritePlaces.length,
itemBuilder: (context, index) {
final place = favoritePlaces[index];
return _buildPlaceCard(place);
},
),
),
],
),
),
);
}
}
关键细节是:首页数据来自本地数据库,即使断网也能看到常用地点。点击卡片后先检查网络,无网时提示"当前网络不可用,已显示上次的路线结果",这是从社区运营那里学来的——老人遇到网络错误会直接放弃,不会像年轻人一样刷新重试。
4.2 公交查询实现:附近站点与到站时间
公交查询的核心流程是:定位 -> 获取附近站点 -> 获取线路到站时间。
服务端提供的接口设计如下:
text复制GET /api/bus/nearby?lng=xxx&lat=xxx&radius=500
GET /api/bus/arrival?stationId=xxx
客户端实现逻辑:
- 进入公交页面,先显示一个"正在定位并查找附近站点"的加载状态。
- 定位成功且拿到站点列表后,按距离排序,默认选中最近的站点。
- 展示该站点的所有线路,每条线路显示:线路号、开往方向、距离本站还剩几站、预计到达时间。
- 每 30 秒自动刷新一次,同时提供手动刷新按钮。
在数据模型上,我定义了一个简洁的 BusLine 类:
dart复制class BusLine {
final String lineName;
final String destination;
final int remainingStops;
final int estimatedMinutes;
BusLine({
required this.lineName,
required this.destination,
required this.remainingStops,
required this.estimatedMinutes,
});
}
这里有个值得注意的优化点:每 30 秒刷新一次会带来大量无效请求,尤其在站点附近但没有公交车进站时。我在服务端做了个合并缓存,同一站点的到站信息 10 秒内不重复抓取高德数据,过期后才重新拉取。这样既保证了实时性,又不会频繁打爆高德的配额。
4.3 路线规划展示:大字版步骤列表的 Flutter 实现
路线规划结果的展示,是整个交通服务模块里最核心的交互界面。它不采用地图为主的形式,而是采用大字号、逐步式、可勾选确认的步骤列表。
我的实现思路:
- 顶部是起点和终点,以及总时长和总里程。
- 下面是一张轻量地图缩略图,只显示整条路线的形状。
- 地图下面才是重点:每一步用一个 Card 渲染,按步骤序号排列。
- 每一步卡片都包含:序号、完整的一句话指令、大箭头指示。
- 每一步完成后,用户可以点击"这一步走完了",卡片变成已完成的灰色,自动滚动到下一步。
这种设计避免了老人在地图上迷路。地图在这里只是辅助,真正的指引是步骤卡片。
核心渲染逻辑:
dart复制class RouteStepCard extends StatelessWidget {
final RouteStep step;
final bool isCompleted;
@override
Widget build(BuildContext context) {
return Card(
color: isCompleted ? Colors.grey.shade200 : Colors.white,
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(
radius: 24,
child: Text('${step.index}'),
),
SizedBox(width: 16),
Expanded(
child: Text(
step.instruction,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600),
),
),
],
),
),
);
}
}
字体大小统一用 24sp 以上,行高 1.5 倍,关键数字用红色或蓝色加粗。比如"乘坐27路公交车,坐5站"中,"27路"用蓝色,因为这是老人最需要记忆的数据。
4.4 语音播报:不能只做视觉适老化
视觉适老化只是基础,语音辅助才是真正的加分项。有一项调研数据显示,很多老人第一步就卡在"看不清字"上,语音播报能有效缓解这个问题。
我实现的语音播报功能比较简单:在路线步骤页加载成功后,自动播报当前步骤的语音,点击"下一步"时播报下一步。播报内容直接复用步骤指令文本,没有额外开发单独的话术模板。
OpenHarmony 上实现语音播报有两种方案:
- 系统 TTS 接口:通过 MethodChannel 调用 OpenHarmony 的文本转语音能力。
- 预合成音频:在服务端用语音合成引擎生成 mp3,客户端播放。
我一开始先用系统 TTS,但发现部分开发板上没有预置中文语音包,TTS 直接静音。后来改成服务端预合成音频方案,稳定很多,但实时性差一些,因为需要等语音文件生成完才能播放。折中方案是:高频短语(如"正在为您规划路线""请上车")走本地音频播放,具体路线指令走服务端合成。
4.5 亲情守护:电子围栏与位置共享的实现与取舍
亲情守护是交通模块里最受子女欢迎的功能。它分两部分:
- 位置共享:老人打开行程分享后,子女可以实时查看定位。
- 电子围栏:为老人设定一个"安全区域",离开区域时自动给子女推送通知。
位置共享的实现相对简单:App 定期把坐标上报到服务端,子女端通过 WebSocket 或轮询拿坐标,在地图上打点。
电子围栏有一个工程问题需要重点说明:端上持续定位非常耗电。如果让老人手机每 5 秒上报一次坐标,半天就没电了。我的方案是分层定位策略:
- 前台使用 App 时,正常频率定位。
- 后台且行程中,降频到每 2 分钟一次。
- 后台且未在行程中,使用系统级低功耗定位,触发"离开围栏"条件时立即高频定位。
这个设计参考了很多出行类App的做法,也经过了社区真实用户的测试——7 天下来,后台耗电占比控制在 6% 以内,老人在接受范围内。
5. 真机适配与性能优化:RK3568 和 RK3588 上的排障记录
OpenHarmony 开发板最常见的两款芯片就是 RK3568 和 RK3588。RK3568 性能偏入门,RK3588 性能强不少,但真机表现和模拟器完全是两个世界。这一节记录我在真机上遇到的性能问题和优化过程。
5.1 两款开发板的运行表现对比
先给出一组实测数据,忽略具体环境差异后的参考值:
| 指标 | RK3568 | RK3588 |
|---|---|---|
| 系统版本 | OpenHarmony 4.0 | OpenHarmony 4.0 |
| App 冷启动时间 | 2.8s 左右 | 1.6s 左右 |
| 地图页面滑动帧率 | 偶尔掉到 40fps | 稳定 60fps |
| 公交列表加载耗时 | 800ms 左右 | 450ms 左右 |
| 内存峰值 | 约 420MB | 约 510MB |
从数据上能明显看出,RK3568 的图形渲染能力是瓶颈。Flutter 是自绘渲染引擎,GPU 负载比普通原生应用更高,所以在低端设备上必须针对性优化。
5.2 启动时间优化:首帧之前,绝不初始化不必要的东西
冷启动时间直接影响第一印象——老人点开App后如果 3 秒内没反应,就可能以为没点中,反复点击,反而让启动更慢。我做了几轮优化后,RK3568 上的启动时间从 3.5s 降到了 2.8s:
- 插件懒加载:把地图插件、语音插件改为首次使用时初始化,不在
main()里统一初始化。 - 移除首页不必要的异步请求:首页优先渲染本地缓存,网络请求在首帧渲染完成后异步执行。
- 关闭 debug 模式:调试时会明显变慢,发布版用 release 模式编译。
- 减少首帧 Widget 层级:首页最外层结构尽量扁平,避免过度嵌套导致首帧构建时间变长。
还有一个很影响启动速度的因素:Flutter 在 OpenHarmony 上首次启动时的 shader 编译。解决方法是开启 Impeller 或预生成 shader 缓存,但 OpenHarmony 版 Flutter 对 Impeller 的支持还不稳定,我在 RK3568 上开过,出现了一些渲染花屏,后来回退到 Skia 后端,稳定性优先。
5.3 地图页面卡帧:从 40fps 到稳定 60fps
地图页面是最容易卡顿的地方,因为要同时处理瓦片图片加载、路线绘制和滚动手势。我在 RK3568 上遇到明显的掉帧,经过性能分析后发现两个问题:
问题一:列表项重建过多。地图下方的路线步骤列表,因为使用了 AnimatedContainer 做状态切换动画,每次状态变化都会触发整列表重建。解决办法是给 ListView.builder 的 item 添加 itemExtent 固定高度,并给步骤卡片包上 const 构造函数,减少不必要的重建。
问题二:Map 没有做离屏缓存。地图区域使用 RepaintBoundary 包裹后,滚动过程中不再持续重绘,只在瓦片加载回来后局部刷新。这个优化对帧率提升特别明显。
问题三:瓦片图片没有被复用。重复滑过同一区域时,瓦片重新从磁盘加载。我实现了一个基于 LRU 的内存缓存,把最近 40 张瓦片留在内存里,滑动时直接拿内存数据,不再走 IO。
5.4 定位耗电优化与后台策略
定位耗电在前面亲情守护里已经讲过部分方案,这里再补充一个实现层面的细节:OpenHarmony 的位置服务支持设置定位频次和精度。我在室外使用高精度模式,在室内检测到 Wi-Fi 环境时自动切到低功耗模式,通过 platform channel 动态调整原生侧定位参数。
还有一点,网络请求的超时时间一定要设置。OpenHarmony 开发板的网络栈在某些场景下表现不稳定,我遇到过请求卡住 60 秒才超时的情况。后来把接口超时统一设置为 8 秒,超时后自动用上次缓存数据兜底,同时提示"网络较慢,已为您展示上次结果"。这个策略在养老场景很关键——稳定 > 实时。
6. 打包上架与后续迭代:不跑完这一步,前面全是白费
功能开发完、真机调试通过,只完成了一半工作量。OpenHarmony 应用的打包签名、隐私合规、灰度发布,每一步都有各自的细节。
6.1 签名与 hap 打包
OpenHarmony 应用的最终产物是 .hap 包,通过 DevEco Studio 的 Build 菜单生成。打包之前需要配置签名,签名文件通过 DevEco Studio 的 Project Structure 界面生成,包含:
.p12证书文件.cer证书.p7bprofile 文件- 签名别名和密码
如果签名不对,应用在真机上装不了。这个问题的排查方式是:看安装时报错信息,如果出现 code: 9568320 这类错误,基本就是签名或证书问题。
6.2 隐私权限声明与合规
交通服务模块涉及的权限包括定位、网络、后台运行、音频播放等。在 OpenHarmony 的 module.json5 中声明权限之外,还有一个容易被忽略的点:隐私政策必须在首次启动时展示,否则上架审核或监管检查会出问题。
我在实现时把隐私弹窗放在启动页之后、主页之前,而且做成"不勾选同意就无法继续使用"的强弹窗。理由是这个项目涉及老人位置数据,属于敏感个人信息,隐私说明必须做充分且清晰,用大字和简明语言告知老人"这个App会记录你的位置,用于防止走失,位置信息只对你的家人可见"。
6.3 灰度发布与用户反馈的收集
这个项目面向的是社区和养老院,做全量发布风险太大。我的方式是分阶段灰度:先在一个社区试点运行两周,收集问题、调整体验,再逐步扩展到其他社区。
灰度期间,我额外加了一个"用户操作日志"功能,记录关键页面的点击路径、异常页面停留时间和崩溃日志。不是做什么用户跟踪,而是为了快速定位"老人卡在哪一步"。日志会上报到自己的服务器,数据脱敏,只保留操作行为,不涉及个人敏感信息。
6.4 一点后续规划
交通服务上线只是开始。我手里还有一份"待做清单":接人功能(子女帮老人叫车)、无障碍模式(适配读屏软件)、以及家人协同维护常用地点的 Web 端。这些东西如果一开始就全做,项目周期会拖得很长,反而不利于快速验证需求。先做最核心的、最能解决实际问题的功能,让用户先用起来,再根据反馈逐步迭代,这才是 To B 智慧养老项目最务实的节奏。
