我最近把一个健康档案管理系统的“快速入口”搬到了 OpenHarmony 设备上,整套 UI 和业务逻辑全部用 Flutter 写完,再通过社区维护的 OpenHarmony 分支跑通真机。整体项目不算复杂,但里面涉及环境搭建、跨端能力桥接、设备适配和一堆“文档里根本不会写”的坑,值得单独整理一篇实战记录。如果你也在评估 Flutter 能不能做 OpenHarmony 应用,或者已经准备上手但卡在环境或运行环节,这篇文章应该能帮你少走不少弯路。
先说结论:Flutter 做 OpenHarmony 应用目前可行,但远没有到“开箱即用”的程度。它能带来一套 Dart 代码同时覆盖 Android、iOS、OpenHarmony 甚至桌面端的收益,代价是你需要接受非官方分支带来的版本延迟、部分插件不可用、以及调试工具链不完整的现实。对于健康档案这类对 UI 一致性要求高、需要快速迭代但又没有极其复杂系统能力的应用,这个组合反而非常合适。下面我会从项目定位讲起,逐步展开环境配置、核心页面实现、原生能力调用和一系列踩坑经验。
1. 项目在做什么:健康档案快速入口与跨端口标
1.1 这个项目最想解决的三个问题
先明确“快速入口”不是完整健康档案系统。实际业务里,完整系统包含个人信息、历次就诊记录、检验报告列表、用药计划、医生排班等一大堆模块,用户的手机不可能被所有低频操作占据。很多人打开一个健康类 App,核心诉求就是:看最近一次报告、记着按时吃药、知道自己下次复查时间。所以我这个快速入口只做三件事:把跟当前用户最相关的健康摘要聚合到首页,把高频使用功能做成一键卡片,再预留一个可跳转完整档案系统的路由。
第二要解决的问题是终端碎片化。健康档案类应用经常需要跑在医生手持终端、护士站平板、患者自己的 Android 手机上,甚至有项目会把触屏一体机纳入范围。如果每个设备都写一套原生界面,人力成本直接翻好几倍。引入 Flutter 之后,Dart 代码在这些终端上渲染的像素级效果几乎一致,唯一要适配的就是屏幕尺寸和交互方式。
第三是可维护性。OpenHarmony 自身的 ArkUI 声明式语法学习成本并不低,而且它目前还不能让一套代码反向跑到 Android/iOS 上。相比之下 Flutter 的生态、组件库和社区问答都非常成熟,大部分业务团队招到一个 Flutter 工程师就能同时维护多个端,这个优势在当前资源普遍紧张的项目环境里非常有吸引力。
1.2 为什么是 Flutter × OpenHarmony,而不是 ArkUI 或 Android 原生
我见过一些团队的方案是“Android 一套,OpenHarmony 再招人用 ArkUI 重写一套”。这样做 UI 能做得非常原生,性能也很好,但代价是双倍测试成本、双倍埋点成本和长期维护中两个端的交互不一致。健康档案应用恰恰对一致性有要求:同一份电子病历展示在 Android 和 OpenHarmony 设备上,如果出现字号、间距、字段顺序不一样,医护工作者很容易误读。
另一个备选是 ArkUI 跨端方案。ArkUI 的声明式写法和 Flutter 有不少相似之处,但它的生态目前主要服务 OpenHarmony 产品,想把它跑到 Android/iOS 上基本还是要套一层 WebView 壳。如果业务的上线范围主要是“OpenHarmony + Android”组合,Flutter 在当前阶段是相对均衡的选择:它拥有成熟的跨端社区,又有 OpenHarmony SIG 在持续适配引擎和插件。
不过要诚实说一句:Flutter 官方至今没有把 OpenHarmony 列入正式支持列表,这意味着你不能直接 flutter create 一个官方模板就万事大吉。我这里用的是 OpenHarmony 社区维护的 flutter_flutter 和 flutter_packages 仓库,属于“跟随分支但滞后于官方版本”的模式。做项目前一定要和团队打好招呼:线上如果出现 Flutter 版本升级带来的问题,处理优先级永远低于官方支持的 Android/iOS 构建。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:让 Flutter 能跑到 OpenHarmony 上
2.1 安装 Flutter SDK 时最容易耽误时间的几个点
先装 Flutter SDK,这个步骤和普通 Flutter 开发完全一样。下载压缩包后解压,把 bin 目录加进 PATH。如果是 macOS 或者 Linux,建议顺手做一次国内镜像配置,否则后续拉起 pub 依赖时经常等很久甚至失败。配置方法是在 shell 环境里加入两行:
bash复制export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
网上很多教程会让你把这行写进 ~/.bashrc,但不少人改完直接在当前终端跑 flutter --version,发现没有任何变化。不是配置没写对,而是当前终端没有重新加载配置文件。执行 source ~/.bashrc,或者干脆新开一个终端窗口,再跑 flutter doctor 就正常了。这个细节看起来很小,实际能把很多刚入门的人卡住十几分钟。
安装完后用 flutter doctor 检查环境。注意如果你只做 OpenHarmony 开发,Flutter 官方 doctor 可能不会显示 OpenHarmony 工具链状态,别慌。接下来要准备 OpenHarmony 的 SDK 和 IDE,主要路径是通过 DevEco Studio 下载,也可以直接用命令行 SDK 包。我建议把 DevEco Studio 安装好,因为后面查看日志、签名、打包都要用到它的配套工具。
2.2 搭建一个能同时编译 Android 和 OpenHarmony 的 Flutter 工程
OpenHarmony 对 Flutter 的支持并不是直接集成在官方 Flutter 仓库的 stable 分支里,而是由 OpenHarmony SIG 维护了一套独立的 flutter_florch、flutter_packages 和 flutter_samples。实际操作前,你需要先确定要用的 OpenHarmony SDK 版本,然后切到 flutter_flutter 仓库中对应的分支。
有一个常见的误区是下载了官方 Flutter SDK,然后直接拿 OpenHarmony 的 flutter_packages 去执行 flutter pub get。如果版本基准相差太远,编译时会报一堆莫名其妙的符号缺失或 C++ 编译错误。尽量把 Flutter、OpenHarmony SDK、Flutter Packages 三者的基准版本对齐,通常的做法是:去 Gitee 上找 openharmony-sig 组织下的 flutter_flutter 仓库,查看 README 中推荐的 SDK 版本组合,再决定下载哪个分支。
在创建工程时,我建议用一个普通 Flutter 工程做骨架,再把 OpenHarmony 需要的 ohos 目录及相关配置补充进去。如果你的 flutter_flutter 仓库已经带了 Flutter 3.x 以上版本,运行 flutter create 后生成的目录里多数会包含 ohos 文件夹选项;如果没生成,就去 flutter_samples 里找一个带 ohos 目录的官方样例,把模板结构复制过来改包名。
环境就绪后,命令行可以识别出 OpenHarmony 设备。通过 flutter devices 查看硬件,如果能看到设备 ID,说明工具链连接成功。我之前遇到最头疼的情况是设备能被 hdc list targets 看到,但 Flutter 侧始终不识别,检查到最后发现是启动应用时缺少对应平台的 --device-timeout 或者 SDK 路径没导出的问题。建议把 OpenHarmony SDK 的路径写进环境变量:
bash复制export OHOS_SDK_HOME=/path/to/ohos-sdk
export DEVECO_SDK_HOME=/path/to/deveco-sdk
这两个变量不同项目里叫法可能不一样,一定要看目标 flutter 分支的文档确认,否则编译阶段会出现 Unable to locate SDK 类的错误。
2.3 通过镜像和依赖管理避开下载大坑
OpenHarmony 相关的 Flutter 工程里,pubspec.yaml 中通常会有一些从 gitee 或特定仓库拉取的依赖插件。如果你在解析依赖时发现卡住,先去看 pub 镜像是否配置正确。把 PUB_HOSTED_URL 配好后,绝大部分 dart 包能顺利下载。少数插件如果托管在私有仓库或 GitHub Releases 上,镜像帮不上忙,这时候可以把下载好的压缩包放到 pub 缓存目录,或者改用 path 依赖。
另外,OpenHarmony 的构建过程会用到 Gradle、CMake、Ninja 这些底层构建工具。Flutter 在编译 Android 时需要下载 Gradle 版本和 Android SDK 组件,OpenHarmony 的某个分支可能同时还会拉取 OHOS 的 native 依赖。不要只盯着 flutter pub get 的输出,多留意终端里是否有 “Downloading…” 字样带着固定仓库地址的请求。如果网络环境对这些仓库不通畅,建议提前联系项目组确认内部镜像源,把 ~/.gradle/init.gradle 或 pubspec.lock 里对应的仓库地址替换成能访问的地址。
我自己的经验是:准备好所有依赖后,不要第一次就直接连真机跑完整应用。先在里面用一个最小页面跑通 Hello World,确认 Flutter 引擎能在 OpenHarmony 设备上成功渲染,再往里面加业务代码。因为 Flutter 在 OpenHarmony 上的调试链路跟 Android 还是有差别,如果一开始就夹带了几十个插件,出问题后很难定位是引擎问题、插件问题还是自己的页面代码问题。
3. 健康档案快速入口核心页面实现
3.1 快速入口的设计原则与交互决策
健康档案这种应用,界面设计的核心不是“花哨”,而是“减少理解成本”。对于患者来说,他打开应用后最想知道的是最近一次检查结果是否正常;对于医生来说,他希望在半秒内找到某个患者的既往过敏史。因此我把首页设计成两区:顶部是一个“当前状态摘要卡片”,显示最近录入的三项关键指标和一条文字结论;下面是高频功能格子,包括“体检报告”“用药提醒”“测量记录”“档案详情”四个按钮。
这种布局在 Flutter 里用一屏 CustomScrollView 就可以完成,不需要引入复杂的页面栈。真正需要思考的是点击卡片后如何跳转:如果四个卡片都直接 push 到新页面,页面层级会很深。我的做法是给“档案详情”这个卡片配置普通路由,而“体检报告”“用药提醒”“测量记录”这三个卡片共用同一个报告列表页,通过不同 tab 参数区分数据源。这样用户点进去只会看到列表和详情,不会迷失在反向导航的迷宫里。
因为健康档案在用户生命里属于低频刚需,交互路径能短就短。我不建议在启动页放轮播图或运营位,那些东西确实能提升业务点击数据,但会掩盖核心信息,被老人或者不熟悉手机的患者使用时反而成为负担。快速入口的核心原则是:一步看到状态,两步拿到结果。
3.2 首页卡片与路由实现的 Dart 代码思路
页面结构用自定义 Widget 拆分会很清晰。首先是 ActionCard,用来承载高频功能格子,它对外只暴露标题、副标题、图标和点击回调,内部用 Material 和 InkWell 做点击反馈。这里不直接用 GestureDetector 的原因很简单:Material 组件在 Flutter 里自带水波纹和无障碍语义,对于健康类应用来说,点击反馈和无障碍支持不是可选项而是刚需。
dart复制class ActionCard extends StatelessWidget {
const ActionCard({
Key? key,
required this.title,
required this.subtitle,
required this.iconData,
required this.onTap,
}) : super(key: key);
final String title;
final String subtitle;
final IconData iconData;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(16),
elevation: 0,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(iconData, size: 28),
const Spacer(),
Text(
title,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 4),
Text(
subtitle,
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
);
}
}
首页用 SliverGrid 来排布这些 ActionCard,照顾到手机和平板两种场景。交叉轴数量不能写死:手机竖屏 2 列最合适,因为列数太多卡片就会变小,标题很容易换行;在平板上 4 列才比较合理。我用 LayoutBuilder 获取当前宽度,根据宽度决定 crossAxisCount 和 childAspectRatio。宽高比建议在 1.4 到 1.7 之间,比例太大卡片显得太扁,太小又会浪费垂直空间。
dart复制LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
final columns = width > 600 ? 4 : (width > 360 ? 2 : 2);
final ratio = width > 600 ? 1.4 : 1.5;
return GridView.builder(
padding: const EdgeInsets.all(12),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: ratio,
),
itemBuilder: (context, index) {
// 根据 index 返回对应的 ActionCard
},
);
},
)
这里有一个容易被忽视的点:健康档案的主色调通常选蓝色或青色,它传递“可信赖”的感觉,但不要只依赖颜色表达状态。像指标异常、有未读报告这样的提醒,必须配合文字角标或图标,否则红绿色弱用户很容易错过重点。
3.3 缓存、Token 与隐私保护怎么处理
快速入口如果每次打开都请求后端接口,体验一定会被网络延迟拖累。健康档案类 App 的合理策略是:服务端返回关键摘要后,客户端做一层本地缓存,启动时先渲染缓存内容,再异步刷新最新数据。对 Flutter 开发者来说,本地缓存有两个选择:shared_preferences 适合存小结构,hive 或者 drift 适合存复杂的本地数据。
但使用 shared_preferences 前必须确认它支持 OpenHarmony 平台。官方 Flutter 插件市场里大部分插件默认支持 Android/iOS,OpenHarmony 支持通常需要替换为 openharmony-sig 维护的 fork 版本。在 pubspec.yaml 里配置依赖时,可以通过 git 依赖直接指向 gitee 上维护的仓库。如果某个插件没有 ohos 版本,我会在业务层再包一层接口,先让 Android 端用原版,OpenHarmony 端用自定义实现,后面找到适配版本后只需要替换内部实现。
涉及到健康数据隐私,有几条底线不能碰:不要缓存身份证号、完整病历、化验单扫描件这类敏感文件;缓存刷新时间控制在 5 到 10 分钟;页面离开后立即清理敏感输入框。如果是演示项目,也不要直接把真实的用户健康数据写死进 assets 里,万一包体流传出去就是安全事故。合理做法是放几条脱敏假数据,并给首页底部加一个“演示模式”标识,避免使用方误把测试数据当真实结果。
4. 跨端原生能力桥接:图库、外设与通知
4.1 理解 Flutter 和 OpenHarmony 之间的通信机制
Flutter 渲染 UI 时并不直接使用 OpenHarmony 的原生控件,但涉及到系统能力,比如相册选图、蓝牙连接、USB 设备访问、本地通知,就必须通过平台通道桥接。Flutter 官方框架中三种常用通道分别是 MethodChannel、EventChannel 和 BasicMessageChannel。健康档案快速入口里绝大多数需求都是“用户点一下,调原生能力,返回一个结果”,所以用 MethodChannel 足够;如果后面要做实时心率监测,需要持续从设备拿数据,那再补充 EventChannel。
很多人第一次接触平台通道时会觉得抽象,其实可以把它理解成“两个 App 之间打电话”。Dart 端拨号并等待答复,原生端接听电话,处理完说一句话挂断。这个电话过程不能传大文件或大量二进制数据,传一个超长 base64 图片非常可能把通道卡死。正确做法是原生端先把图片存到公共缓存目录,只把文件路径通过通道返回给 Dart 侧,Flutter 再用 Image.file 去读。这样既快又不占通道资源。
4.2 一个实际例子:从 OpenHarmony 系统相册选择健康报告照片
健康档案有一个高频使用场景:用户把纸质的体检报告或检验单拍照存档。这个页面在 Flutter 里包含一个“添加报告照片”按钮,点击后需要调起 OpenHarmony 系统相册,拿到图片后展示缩略图。Dart 侧封装一个全局单例类,统一入口:
dart复制class NativeBridge {
static const MethodChannel _channel = MethodChannel(
'com.example.healthhub/native',
);
static Future<String?> pickImage() async {
try {
final String? path = await _channel.invokeMethod<void>('pickImage');
return path;
} on PlatformException catch (e) {
debugPrint('pickImage failed: ${e.message}');
return null;
}
}
}
OpenHarmony 原生侧需要用 ArkTS 或者 C++ 完成真正的相册调用。当前 OpenHarmony 推荐的图片选择接口是 @ohos.file.picker 里的 PhotoViewPicker,它允许用户在系统相册中授权图片,不需要在 module.json5 里额外声明存储权限。选中图片后,通过 photoAccessHelper 或 fileIo 把 uri 转成能在 Dart 侧读取的路径,再通过 MethodChannel 回传。
示例调用过程的思路大致如下:原生侧注册 MethodChannel 时,监听名为 pickImage 的方法,执行系统 picker 动作。拿到用户选中结果后,将图片拷贝到应用沙盒缓存目录,返回给 Flutter 侧一个 file:// 或相对沙盒路径。这里切记不要在通道里返回 content:// 类型的 uri,因为 Dart 侧如果直接拿这个 uri 给 Image.file 用,会发现 Android 和 OpenHarmony 的权限体系完全不同,读取不了。
4.3 扩展能力:USB 设备、蓝牙体脂秤与本地通知怎么接
健康档案未来大概率要接入智能硬件,比如血压计、体脂秤、血氧仪。OpenHarmony 设备上这类硬件接入通常通过蓝牙或 USB 总线完成。Flutter 侧没有直接的 USB 权限管理能力,正确的处理方式是在 OpenHarmony 原生侧写一个插件,利用 @ohos.usbManager 或者社区里的 libusb 适配层完成设备枚举、权限申请、批量读写,然后通过 MethodChannel 往 Flutter 传“设备已连接”“收到一条测量数据”这类高频事件。
做得再好一点,可以让原生侧专门开一个常量池,保存最近的 USB 数据包。Flutter 端每秒钟调用一次 getLatestSample(),然后渲染成折线图。如果测量过程要求毫秒级响应,也可以考虑 C++ 插件直通 layer,但这对团队能力要求高了不少,不是快速入口这种轻应用的首选。
本地用药提醒也是一个典型的原生能力。Flutter 的 flutter_local_notifications 插件很成熟,但 OpenHarmony 支持情况需要单独确认。如果插件不兼容,可以在 OpenHarmony 原生侧用 NotificationKit 创建通知渠道,把提醒时间传给原生侧后由系统定时调度。快速入口的应用场景中,提醒只需要覆盖“到点该吃药”这一件事,实现成本并不高。
5. 真机适配、性能优化与构建发布
5.1 RK3568 和 RK3588 开发板上的设备树问题
你可能在开发中遇到过“RK3568 设备树太多不知道选哪个”的问题。OpenHarmony 支持众多第三方开发板,同主控不同板卡的 GPIO、显示接口、触摸屏型号可能不一样,所以内核编译时需要选一个匹配的设备树文件。选择方法不是拍脑袋猜,而是先拿到开发板厂家提供的配置文件,看它的品牌型号;如果找不到,就把 /sys/firmware/devicetree/base/model 文件读出来,里面通常写着板卡名称。
如果 Flutter 应用只是跑在 OpenHarmony 预编译镜像上,你其实不需要手动编译内核和设备树,直接用系统镜像里已经嵌好的配置即可。只有当系统起来后触摸无反应、屏幕方向不对、USB 不通时,才需要回过头去检查设备树。我踩过最深的一个坑是:同一块 RK3588 开发板,销售页面是一回事,板子丝印又是另一回事,从网上下载的 dtb 和实际硬件完全不匹配,最后只能找厂家技术支持要原厂配置,折腾了整整一天。
跑 Flutter 应用层面,RK3568 和 RK3588 的差别主要体现在性能上。RK3568 属于中低端处理器,渲染复杂动画时掉帧会比较明显。开发时尽量把动画控制在透明度和位置变化这类 GPU 友好操作上;如果要用模糊、阴影、复杂的路径裁剪,先在小内存设备上多测几轮。RK3588 性能好不少,但 CPU 大核资源仍然有限,首页不要做大量同步 JSON 解析或磁盘 IO,否则掉帧依旧。
5.2 首屏启动和渲染性能的实测优化
OpenHarmony 设备启动 Flutter 应用的速度不仅取决于 Flutter 引擎本身,还取决于设备存储速度和系统进程调度。优化思路分三层:减少首帧前工作、避免过度绘制、压缩主 Isolate 的阻塞任务。快速入口首页没有复杂的网络依赖逻辑,我把 token 获取和缓存读取全部移到异步回调里,路由首帧只渲染静态骨架页。这样即使缓存查询耗时几十毫秒,用户也感觉不到白屏。
另外,OpenHarmony 渲染路径和 Android 有些不同,遇到页面大面积背景色频繁闪黑,可以尝试把 Flutter 的混合模式参数调低,或者在原生侧把默认背景色改成和白名单页面一致。如果页面中有大图片,不要直接加载原图,先让服务端产出 WebP 缩略图或在本地上采样降级。健康档案的“报告照片”往往很大,一张几千像素的照片直接塞进 ListView,内存直接起飞,我的办法是在展示层统一走 cacheWidth 参数,让 Flutter 解码时把宽度限制到 1200 以内。
5.3 HAP 打包、签名与常见发布问题
Flutter 工程产出 OpenHarmony 应用时,最终产物是 HAP 或 APP 包,不是 APK。从命令行构建需要用到 OpenHarmony 的打包工具,DevEco Studio 里很容易完成签名和构建,但如果走 CI/CD,需要额外配置 SDK 路径和签名证书文件。我习惯把签名文件放到工程外的安全目录,不提交进 Git,并在 CI 的环境变量里注入密码。要记得 HAP 的签名证书有有效期,OpenHarmony 新版本还会要求 profile 文件里声明的 bundleName 和工程配置一致,如果构建报 “signature verification failed”,优先检查这三个值是否完全匹配。
有一点容易被坑:OpenHarmony 构建时可能同时检查应用是否声明了需要的权限。比如快速入口如果计划通过蓝牙扫描测量设备,必须在 module.json5 里声明 BLE 相关权限,否则运行时直接返回 no permission,Flutter 侧得到的错误提示可能很笼统,排查起来很费劲。在 Android 上,我们可以用运行时申请权限,但 OpenHarmony 很多权限属于系统级,不一定能通过应用弹窗动态申请。做功能排期前就应该把权限清单梳理清楚,避免开发到一半发现某个系统能力在普通应用权限体系下根本拿不到。
6. 实测中遇到的高频问题与排查记录
6.1 一个速查表,覆盖我遇到的典型故障
项目推进过程中,我把群里和论坛里高频出现的问题汇总了一下,下面这张表不算完整,但基本都是“症状明显、原因隐藏”的典型。
| 问题描述 | 可能原因 | 解决办法 |
|---|---|---|
| 依赖下载一直失败或卡住 | pub 镜像未配置或配置不完整 | 设置 PUB_HOSTED_URL 和 FLUTTER_STORAGE_BASE_URL,重启终端 |
| 下载提示“will be downloaded from storage.flutter-io.cn” | 官方默认源切换到了中国镜像 | 属于正常提示,确认下载完成后可关闭 |
| 编译报错:You are applying Flutter’s main Gradle plugin imperatively | Android 工程同时混用 apply 和 plugins DSL | 只保留一种 Gradle 插件引入方式 |
| Visual Studio CMake generator 相关报错 | 误把 Windows 桌面构建参数带进了 ohos 工程 | 先确定用的是 flutter run -d ohos,不要混用桌面构建目标 |
| CheckboxListTile 文字离按钮太近或太远 | contentPadding 和 visualDensity 默认值不合适 | 在 ListTileTheme 里覆盖 contentPadding 和 visualDensity |
| 热重载后页面没更新 | 设备不在调试模式 / 连接掉线 | 检查 flutter attach,确认连接后重新触发热重载 |
| 终端提示 PATH 需要新终端生效 | shell 配置文件改动后未 source | 执行 source ~/.bashrc 或重开终端 |
6.2 逐个展开:典型问题的搜索思路和修复过程
先说依赖问题。OpenHarmony 的 Flutter 工程依赖源可能同时涉及 pub.dev、Gitee、GitHub Releases 等多个仓库。如果你能看到 flutter assets will be downloaded from https://storage.flutter-io.cn 这段话,其实不一定是报错,它只是提示 Flutter 官方把资源下载地址换成了中国镜像节点。真正会卡住的是后续某个包长时间没有进度输出,这时候去 pub 缓存目录看有没有临时文件,如果存在且大小不再变化,基本可以判断是仓库下载超时。
再说 Gradle 插件冲突。OpenHarmony 工程的 Flutter 插件有些是从 Android 工程复制过来的,里面如果既有旧的 apply plugin: 'com.android.application' 写法,又有新的 plugins { id "com.android.application" } 声明,构建时会报出 “You are applying Flutter’s main Gradle plugin imperatively using the apply script” 这类错误。解决办法很简单:打开 settings.gradle 和根目录 build.gradle,把新旧两种写法统一成一种。在实际操作中,我会先把新式 plugins 块注释掉,用旧的 apply 方式先跑通一个最小构建,验证没问题后再改回来。
还有一个被热搜词标记的问题:CheckboxListTile 文字距离按钮太近。这个组件在 Flutter 里默认会通过 contentPadding 控制文字和复选框的间距。如果你加了 dense: true,间距会被压缩得很紧凑。调整时不要分别给每个 CheckboxListTile 设置 padding,那样非常难维护。更合理的做法是用 ListTileTheme 统一设置:
dart复制ListTileTheme(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
child: CheckboxListTile(
title: Text(reminder.title),
value: reminder.isEnabled,
onChanged: (value) {
// handle change
},
),
)
很多 UI 细节问题都源于组件默认值,而不是代码本身写错。整理这些默认值的有效方式是把 flutter 版本升级后的 visible 变更日志看一遍,不能只盯着自己的业务代码。
6.3 热重载不更新其实是调试链路问题
Flutter 热重载在 Android 模拟器上很顺手,到了 OpenHarmony 设备上却经常失灵。我遇到最典型的情况是终端显示 “Hot reload” 执行成功,但设备屏幕纹丝不动。排查顺序是:先看应用进程是否真的处于 debug 模式,如果之前不小心用 flutter run --release 启动过,那热重载本来就不会生效。再次确认设备连接状态,通过 flutter attach 重新挂载一次进程,然后再按小写 r 触发热重载。
如果设备在 OpenHarmony 上的渲染线程卡住了,热重载也不会实时刷新,更常见的表现是页面没有任何响应。这时候不能只靠重启应用解决问题,要先杀掉设备上的进程,重新 flutter run。热重载并不总能恢复所有状态,尤其在修改了原生插件和平台通道之后,它对原生层的改动非常敏感,正确做法是直接重新冷启动。
7. 再往后:这套工程还能怎么继续扩展
健康档案快速入口本身是轻量应用,但它留了不少扩展余地。如果后续团队决定把 OpenHarmony 也纳入正式支持列表,我想先在本地通知、文件下载、扫码这几个模块补充原生插件,然后做 UI 自动化回归。Flutter 的状态管理在这个项目里没有用复杂库,只用了 InheritedWidget 加 ValueNotifier,原因就是轻应用页面少、状态简单,没必要引入额外包,增加 OpenHarmony 兼容成本。
有一点值得提醒:在用 Flutter 开发 OpenHarmony 应用时,最好每接一个新插件就立刻在真机上跑一遍最小验证。官方平台的插件能编译通过,不代表在 OpenHarmony 上运行时行为一致。比如文件路径处理、权限回调、网络代理这些逻辑,两个系统的差异比想象中大。先写一个测试页面把插件能力逐项点亮,后面业务开发才不会被底层兼容问题反复打断。
最后分享一个我的实际操作习惯:我会用一个公共的 doc/compatibility.md 文件记录每个插件在不同平台上的表现,里面包含测试日期、设备型号、Flutter 版本和遇到的问题。这个文件在项目复盘时价值非常高,因为跨端开发最怕的不是代码写不出来,而是同一个功能在不同设备上行为不一致,等测试报过来再一个个排查,成本远高于开发时多花十分钟记录。
