做 Flutter 的人这两年很难绕开 OpenHarmony,做 OpenHarmony 的人又很难绕开生态不够的问题。我正好卡在这两个圈子的交叉点上,用 Flutter 给 OpenHarmony 做了一款“逆向思维训练 App”,里面还带了一个完整的学习日历模块。这篇东西不是课程,也不是官方文档翻译,就是把我从环境搭建、架构选择、核心功能实现到打包上真机的完整过程,连坑带方案一起写出来。如果你也想在 OpenHarmony 上用 Flutter 做点实际项目,或者正打算做一个带打卡、日历、进度追踪的学习类 App,这篇应该能帮你省掉不少自己踩坑的时间。
先说一下最终做出来的东西是什么样的:一个跑在 OpenHarmony 设备上的 Flutter 应用,核心是两个模块。第一个是“逆向思维训练”,内置了几种典型题型,每次随机出题,用户作答后立即反馈,并记录正确率。第二个是“学习日历”,把每天的做题记录、打卡状态、连续学习天数全部落到本地数据库里,以日历视图展示,过去哪一天学了、哪一天断了,一目了然。两个模块不是割裂的,做题训练的结果会自动写入日历,形成一条“训练 → 记录 → 反馈 → 坚持”的闭环。下面我会按实际开发顺序,把每一步的关键细节和当时的取舍都交代清楚。
1. 项目整体设计与技术选型拆解
1.1 为什么要用 Flutter 做 OpenHarmony 应用
先说结论:因为 Flutter 在 OpenHarmony 上已经可以跑,而且跑得动,虽然还不是官方主推的“第一公民”路线,但对团队和个人开发者来说,是非常划算的一条跨端方案。
如果你只做 OpenHarmony 一个平台,那直接学 ArkTS + ArkUI 写原生应用肯定没毛病。可我这边的情况是,团队已经有了一套成熟的 Flutter 代码库,里面包含了题库管理、用户进度同步、UI 组件库等大量沉淀。如果为了 OpenHarmony 单独用 ArkTS 重写一套,等于所有业务逻辑都要双份维护,成本直接翻倍。而 Flutter for OpenHarmony 这条路线允许我复用 Dart 层的全部业务代码,只针对平台层做适配,这正好踩中了跨端开发最核心的诉求:一套逻辑,多端复用。
另外从性能角度说,Flutter 的渲染引擎是自己基于 Skia 实现的,不依赖系统 WebView 和原生控件,因此在 OpenHarmony 这种系统控件生态还在快速迭代的平台上,反而能保证 UI 的一致性和稳定性。实际跑下来,在 RK3568 这种中低端设备上,列表滚动和页面切换的帧率表现也在可接受范围内。对比之下,RN for OpenHarmony 虽然也在推进,但三方组件生态和 Flutter 比还是薄不少,所以我最终选型时没有任何犹豫。
1.2 两个核心模块的功能闭环设计
这个 App 如果只是“做几道题”或者“记个日历”,其实没什么值得写的。关键在于我把两个模块拼成了一个完整的学习闭环,整个设计逻辑是这样的:
- 用户进入训练模块,选择题型和题量,开始一组逆向思维题目的作答。
- 每题作答后,系统立即给出判定和解析,并同步更新本地统计数据。
- 用户完成整组训练后,本次训练结果(日期、题数、正确数、用时)写入学习记录表。
- 学习日历读取这些记录,自动在对应日期上打上“已学习”标记,并统计连续打卡天数。
- 日历上的历史数据反过来成为用户继续训练的动机:断了一天就前功尽弃,这种轻微的“损失厌恶”对学习类 App 非常有效。
这样的设计让两个模块互相支撑,而不是各自为战。训练模块为日历提供数据来源,日历模块为训练提供持续动力,整个 App 不再是一个功能堆砌的 demo,而是一个有内在逻辑的学习工具。我在写代码之前还专门画了一张数据流向图,把每个页面会读哪些表、写哪些字段先理清楚,这个习惯在后续开发中帮了大忙。
1.3 技术方案与依赖清单
整个项目用到的核心技术栈如下:
- Flutter SDK(OpenHarmony 适配版本)
- OpenHarmony SDK(通过 DevEco Studio 管理)
- Provider 做状态管理
- sqflite 兼容层做本地数据库存储(不加这一层,直接用文件存 JSON 也不是不行,但数据查询和统计会很痛苦)
- intl 做日期格式化
- 自绘日历组件,没有用第三方日历库
这里要特别说明一下第三方库的选择策略。OpenHarmony 的 Flutter 生态还在快速补齐,很多 Flutter 插件在 OpenHarmony 上根本没有对应实现。我踩过最大的坑就是兴冲冲地引入一个在 Android 上表现很好的包,结果一编译直接报错找不到平台实现。所以我的原则是:能用 Dart 纯逻辑实现的功能,绝不依赖平台插件;必须用到平台能力时,优先选官方适配过 OpenHarmony 的包,或者干脆自己写一个极简的 platform channel。
日历组件就是按照这个原则处理的。一开始我尝试引入 table_calendar,在 Android 上非常好用,但在 OpenHarmony 上编译时直接炸了。后来一查,问题出在它依赖的某个内部组件没有 ohos 实现。最终我选择自己用 GridView 画一个日历网格,逻辑不复杂,却能完全掌控样式和交互,反而比套第三方库更顺手。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与工程初始化实战
2.1 开发环境清单与版本注意事项
在写任何业务代码之前,得先把环境搞定。这一步劝退了不少人,因为 Flutter for OpenHarmony 的环境配置比标准 Flutter 多好几个环节。我整理了一份相对完整的清单,照着做基本能一路走到底。
首先是 Flutter SDK。不要用 flutter.dev 官方下载的那个标准版,要用 OpenHarmony 社区维护的分支版本,它在标准 Flutter 基础上增加了 ohos 平台目录的支持。装完之后在终端执行 flutter doctor,你会看到多出 ohos 相关的检查项。然后是 OpenHarmony SDK,这个通过 DevEco Studio 的 SDK Manager 安装即可,它会自动下载配套的 toolchains 和 platform 包。
编译构建这块,建议在工程里保留标准 Android 目录不要删,因为 flutter create 生成的模板工程默认带 android/ 目录,很多基础命令和配置检查会扫描它。真正的 OpenHarmony 平台代码在 ohos/ 目录下,这是适配分支新增的内容。我见过有人手痒把 android 目录删了,结果 flutter 命令直接报错,完全没有必要省这点空间。
设备的连接工具是 hdc,它是 OpenHarmony 的设备调试命令工具,对应 Android 的 adb。安装完 DevEco Studio 后 hdc 会集成在工具链里,但命令行里不一定直接能用,需要手动把它加入 PATH。我后续所有真机调试都靠它,所以拿到新环境第一件事就是验证 hdc 是否可用。
2.2 从零创建一个 ohos 平台工程
工程创建这块,我踩过一次印象极深的坑,必须单独写出来。用标准的 flutter create 创建工程后,默认只有 android/ 和 ios/ 目录,并没有 ohos/。要生成 OpenHarmony 平台代码,需要用适配分支提供的命令,类似:
bash复制flutter create --platforms ohos .
或者从 OpenHarmony 的官方示例仓库拉一个带 ohos 配置的模板工程,然后把自己的业务代码迁进去。我当时图省事,直接用标准 create 出的工程开始写业务层,写了一个星期才发现 ohos 目录压根没生成,场面一度非常尴尬。所以第一步一定要确认工程里有完整的 ohos/ 平台目录结构,确认之后再开始动业务代码。
ohos/ 目录内部的结构和 Android 工程的 gradle 类似,有自己的 build 配置体系。应用包名、版本号、权限声明都要在 ohos/ 工程里单独配置,不会自动和 Android 的 AndroidManifest.xml 同步。权限方面,我这个 App 只用到了本地存储,不需要申请网络权限,所以配置很简单,但如果你的应用要联网,记得在 ohos 的 module.json5 里显式声明网络权限,这是 OpenHarmony 应用与 Android 应用一个很大的区别。
2.3 国内镜像与依赖下载配置
Flutter for OpenHarmony 在国内开发,镜像配置几乎是必备操作。环境变量里面最核心的这两个,务必提前配好:
bash复制export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
不配置的话,第一次 flutter pub get 拉取 packages 会很慢,而且经常中途超时。配置完这两个环境变量后,pub 包下载速度会有质的提升。如果你在下载 Flutter engine 产物或者构建中间件时仍然看到类似 “Flutter assets will be downloaded from https://storage.flutter-io.cn” 的日志,说明环境变量已经生效,正在走国内镜像,不用再管它。
还有一个经常被忽略的点:pubspec.yaml 里依赖的版本号,尽量锁定到一个你验证过的具体版本,而不是用 ^ 通配符随意浮动。OpenHarmony 适配版的 Flutter 框架更新节奏比较快,某些新版本的第三方库会引入不兼容的 API。我在项目中用了一个思路:把一个能正常编译运行的 pubspec.lock 文件存到项目仓库里,作为“已知可用”的基线。后续想升级哪个包,单独升级单独验证。这个习惯让我避免了好几次版本更新引发的连环编译错误。
3. 逆向思维训练模块的设计与实现
3.1 题型设计与题库结构
逆向思维训练这个模块的核心不只是“出题做题”,而是要把“逆向思维”这个抽象概念落地成可训练、可量化的题型。我设计了三种基础题型,也是目前逻辑推理类 App 里最常用的三类:
- 真假判断:给出一段前提条件和一个结论,判断结论是否必然成立。这类题训练的是对充分条件和必要条件的辨别能力。
- 逻辑排序:打乱若干事件或条件的顺序,要求选出正确的逻辑链条。这类题训练的是因果顺序思维。
- 数字规律:给出一组有隐藏规律的数字,选出下一项。这类题训练的是找反常规规律的敏感度。
题库在 App 启动时从 assets 目录加载,以 JSON 格式存储。每一道题的 JSON 结构设计成这样:
json复制{
"id": "tm_001",
"type": "judgement",
"question": "所有A都是B,所有B都是C,那么以下哪个结论必然成立?",
"options": ["所有C都是A", "有些C是A", "所有A都是C", "所有C是B"],
"answer": 2,
"analysis": "由前提可得A→B→C,因此所有A都是C,所以选C。",
"difficulty": 2
}
之所以把解析字段单独拿出来,是因为学习类 App 的核心价值就是“做题后能得到反馈”,没有解析的题目等于让用户瞎猜,留存率会非常低。题库中每道题都有难度等级,后续可以根据用户的历史正确率做动态调整,这是我在 v1.0 里预留的优化方向。
3.2 出题逻辑与状态管理
出题逻辑看起来简单,实际上有几个细节值得抠。第一是“不重复原则”,同一组训练内尽量避免连续出现相同的题目,所以每一轮的出题列表在生成时,会从题库中先做一次洗牌,然后依次取出,而不是每次随机抽一道。第二是“题型配比”,一组 10 道题里,真假判断、逻辑排序、数字规律按照 4:3:3 的比例从各自题库中抽取,这样保证每一次训练都有足够的题型覆盖度。
状态管理我选了 Provider,没有上 Bloc 或者 Riverpod。原因很简单,这个 App 的状态流不复杂:当前题目、答题结果、本轮得分、训练记录,撑死再加一个日历的当前月份。Provider 的 ChangeNotifier 模式完全够用,而且心智负担小,团队里任何人接手都能快速看懂。靠模式复杂来提升项目档次的思路,在中小型项目里往往是负收益。
答题流程的状态流转是这样的:首页选择训练模式 → 进入答题页并加载题目列表 → 用户选择答案 → 立即判定并显示解析 → 点击下一题 → 最后一题答完后自动跳转结果页。结果页展示本轮正确率、用时、错题数,并把训练记录写入数据库。整个过程我用一个 TrainingSession 的 ChangeNotifier 来管理,答题页监听它的状态变化来刷新 UI。
3.3 答题界面与交互反馈的细节
答题界面的交互细节直接决定用户体感,我在这一块花了比业务逻辑更多的时间。每个选项都是一个卡片,用户点击后卡片状态立即变化:选对时高亮成绿色,选错时把选错的项标红,同时把正确项标绿。这个“即时反馈”的交互是学习类 App 的标准设计,千万不能做成所有题答完再统一判分的形式,那样学习的即时激励感就没了。
解析区域默认收起,用户回答后 Slider 展开显示详细的推理过程。这样做有两个考量:一是鼓励用户先独立思考,答完再看答案;二是避免选项旁边长期显示大量文字导致界面拥挤。这里还有一个体验细节:点选选项后,要挡住用户再次点击其他选项,避免用户根据颜色提示去“反推”答案。我在每题作答时设置了一个 locked 标记,配合 0.3 秒的轻微延迟再显示判定结果,既能防止连点,又保留了反馈的节奏感。
页面底部还放了一行当前进度指示:“第 3 题 / 共 10 题”。这个小小的进度条对做题意愿的影响非常大——用户知道终点在哪里,才更有动力把一组题做完。我曾经试过不显示进度,结果从后台数据看,很多用户在做第 3、4 题时就退出了,加上进度显示后整组完成率明显提升,同类产品完全可以参考这个经验。
3.4 训练记录的落库与统计口径
每次训练结束,我会写入一条记录。数据库表结构设计如下:
sql复制CREATE TABLE training_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
train_date TEXT NOT NULL,
total_count INTEGER NOT NULL,
correct_count INTEGER NOT NULL,
duration_seconds INTEGER NOT NULL,
finished_at TEXT NOT NULL
);
train_date 字段专门用来和学习日历模块对接,格式固定为 “yyyy-MM-dd”,避免时间戳在跨天边界带来的归属混乱。统计口径上,我定义了三个核心指标:当日正确率、累计训练天数、连续打卡天数。这里有个细节:连续打卡天数是靠“日期差”算的,如果今天和上次训练日期相差 1 天,连续天数加 1;如果相差 0 天(同一天多次训练),连续天数不变;如果相差超过 1 天,连续天数重置为 1。这个逻辑要想清楚,否则日历上的“连击”数字会出错。
我把这些统计逻辑放在一个名为 StatsRepository 的类里,统一封装数据库查询,页面层不直接接触 SQL。这样做的好处是后续如果要加图表、加周报月报,所有统计逻辑都集中在同一个文件里扩展,不用到处找 SQL 片段。
4. 学习日历模块的实现要点
4.1 日历数据模型与本地存储设计
学习日历模块的第一件事,是定义清楚“一天的学习状态”由什么决定。我的方案是:只要某一天在 training_records 表里存在一条及以上记录,这一天就标记为“已学习”;否则是“未学习”。同时记录当天做题总数、正确数、用时,这些数据在日历单元格上以小角标形式展示。
为了日历查询方便,我没有直接在业务层写 SQL 循环查 30 天数据,而是设计了一条按月查询的 SQL:
sql复制SELECT train_date, COUNT(*) as train_count,
SUM(correct_count) as total_correct,
SUM(total_count) as total_count
FROM training_records
WHERE train_date >= ? AND train_date < ?
GROUP BY train_date;
参数分别传入当月第一天的 00:00:00 和次月第一天的 00:00:00。这样一次查询就能拿到整月所有有训练记录的日期,再放进 Map 里做快速索引,比循环查 30 次数据库高效太多。数据库在 OpenHarmony 上底层是 SQLite,虽然本地量级不太可能成为性能瓶颈,但作为工程师,这种基本的批量查询意识还是要有。
4.2 自绘日历网格的关键逻辑
我放弃了第三方日历库,自己用 GridView.builder 实现日历视图。整个日历网格的基本逻辑是这样的:先算出当前月份的第一天是周几,确定网格前面的空白格数量;然后按 7 列排布,把当月每一天渲染出来。渲染一个日期单元格时,根据该日期是否有训练记录来决定背景色和角标文案。
这里有个关键点:日历的“月视图”需要 42 个格子(6 行 x 7 列)来固定高度,避免用户切换月份时网格跳动。前导空白格和末尾补位格子都渲染成空白占位,但占位格不能参与点击交互。我记录一个小细节:通过 DateTime(year, month + 1, 0).day 可以拿到某个月的天数,这是从 0 日的技巧,比用 DateTime(year, month + 1, 1).subtract(Duration(days: 1)) 要简洁很多,而且不容易踩时区计算的坑。
单元格的视觉设计上,我用了三种状态:普通日期、有训练记录的日期、今天。有训练记录的日期用主题色填充一个小的圆形标记,今天用描边高亮。连续打卡的天数在日历下方单独展示,而且打卡天数的计算逻辑会在每次新训练记录写入后重新触发统计。
4.3 月份切换与视图刷新的状态管理
日历页面的状态比答题页稍微复杂一点,因为它同时依赖“当前显示的月份”和“该月份的训练数据”。我设计了一个 CalendarModel,内部持有 currentMonth 和 monthlyRecords 两个核心字段。用户点击上个月或下个月的箭头时,CalendarModel 会更新 currentMonth,并调用 StatsRepository 异步拉取新月份的数据,数据回来后再通过 notifyListeners 通知 UI 刷新。
这个流程里有一个很容易被忽视的 bug:快速连续点击月份切换箭头时,上一个月份的异步请求还没有返回,用户已经切到了下一个月份,此时如果先返回的数据覆盖了后返回的数据,日历会显示错乱。解决方案是给每次请求加一个序号,只有最新一次请求的结果才允许写入状态:
dart复制int _requestSeq = 0;
Future<void> loadMonth(DateTime month) async {
int seq = ++_requestSeq;
var data = await _repo.getMonthRecords(month);
if (seq == _requestSeq) {
monthlyRecords = data;
notifyListeners();
}
}
这种“过期请求丢弃”的模式在处理所有异步刷新场景时都通用,写页面的时候一定要有意识地处理,不只是日历模块,任何列表刷新、下拉加载都可能遇到。
5. 打包、签名与真机部署全流程
5.1 构建 HAP 与应用签名
OpenHarmony 应用的最终产物是 HAP 包,对应 Android 的 APK。构建流程和标准 Flutter 不同,不能直接靠 flutter build apk 搞定,需要先执行 Flutter 的构建命令生成 Dart 层面的产物,然后进入 ohos/ 目录用 DevEco 的构建工具完成 HAP 的打包。
签名方面,OpenHarmony 和 Android 一样,调试包和发布包需要不同的签名配置。调试阶段最简单的方式是使用 DevEco Studio 自动生成的调试证书,它会在你首次创建工程时把签名信息写入 build-profile.json5。但这里有一个大坑:OpenHarmony 的调试签名和设备的 UDID 是绑定的,换一台设备就需要重新生成签名。所以拿到新设备后第一件事就是查设备 UDID,我用的是 hdc 命令:
bash复制hdc shell bm get --udid
拿到 UDID 后在 DevEco Studio 的签名配置里重新注册,否则安装时大概率会报签名校验失败。我第一次在 RK3568 真机上跑这个 App 时就在这儿卡了一个多小时,每次安装都提示签名信息错误,后来才发现是换了设备没有重新生成签名文件。
5.2 hdc 常用命令与设备调试技巧
hdc 是 OpenHarmony 生态里最常用的调试命令,和 adb 高度类似。以下是我在开发期间使用频率最高的几个命令:
bash复制# 查看当前连接的设备列表
hdc list targets
# 查看 OpenHarmony 系统版本信息
hdc shell param get const.ohos.version
# 查看产品名称
hdc shell param get const.product.name
# 安装 HAP 包
hdc install entry-default-signed.hap
# 查看应用日志
hdc shell hilog | grep myapp
其中 hilog 是排查运行时问题的主阵地。Flutter 层自己的 debugPrint 输出,在 OpenHarmony 的日志里会被打上特定标签,实测下来用 hilog | grep flutter 过滤最有效。如果 App 直接闪退,优先去看 hilog 里有没有 Dart exception 或者平台层报错,大部分问题都能在这里找到直接线索。
5.3 在 RK3568 与 RK3588 真机上的表现
我手里有两台调试设备,一块 RK3568 的开发板,一块 RK3588 的开发板,正好代表 OpenHarmony 目前最主流的两个硬件档位。RK3588 的性能明显更强,整个 App 的页面动画、列表滚动都非常顺滑,基本和普通 Android 中端机体验一致。
RK3568 则是另一个世界。性能差距摆在那里,我在这块板子上发现两个明显的性能瓶颈:一是答题页选项卡片切换状态的动画会有掉帧,二是日历网格在月份切换时,如果有多个日期需要绘制角标,首帧会有一点卡顿。面对这种情况,我的处理思路是“能减就减”:连续打卡的天数统计改为只在数据变化时计算,不再每次进入日历页都实时算;月份切换动画从 300 毫秒缩短到 150 毫秒,并且只对方向指示箭头做动画,日历网格本身直接刷新。这样优化后,RK3568 上虽然称不上极致丝滑,但已经能做到稳定可用的水平。
6. 常见问题与排查技巧实录
6.1 编译期间的典型报错与处理
在这个项目的开发过程中,我前前后后遇到过的编译错误少说也有几十个,挑几个最常见的整理成一张速查表:
| 报错现象 | 根本原因 | 解决方案 |
|---|---|---|
| Applying Flutter's main Gradle plugin imperatively using the apply script method is no longer supported | 新版本 Flutter 的 Gradle 插件要求使用 plugins DSL 声明方式,不再支持 apply 方式加载 | 修改 android/settings.gradle 和 build.gradle,改用 plugins 声明 |
| Could not resolve plugin dev.flutter.flutter-plugin-loader | 插件仓库未配置或版本不匹配 | 检查 ohos 工程的仓库配置文件,确认 flutter-plugin-loader 版本与 Flutter SDK 匹配 |
| pub get 超时或下载失败 | 网络原因导致 pub 仓库访问不稳定 | 配置 PUB_HOSTED_URL 和 FLUTTER_STORAGE_BASE_URL 走国内镜像 |
| 安装 HAP 时提示签名校验失败 | 调试证书与设备 UDID 不匹配 | 用 hdc shell bm get --udid 获取设备 UDID,重新生成签名 |
其中那个 “Applying Flutter's main Gradle plugin imperatively” 的报错,是很多从旧项目迁移到新版 Flutter 分支时会遇到的问题。旧工程里习惯在根目录 build.gradle 中用 apply from 方式引入 Flutter 的 Gradle 插件,新版本直接废弃了这个入口,必须改成 plugins DSL。我那次是直接照着报错提示复制最新模板工程里的 gradle 配置才解决的。
6.2 运行期间的闪退与异常
闪退问题在 OpenHarmony 上排查起来比 Android 麻烦,因为错误日志的格式和常见报错种类都有差异。我遇到最多的一类闪退是:在 Android 上能正常运行的第三方插件,到 OpenHarmony 上初始化时抛出 MissingPluginException。根源是相当一部分 Flutter 插件只实现了 Android 和 iOS 平台端,OpenHarmony 没有对应的 Dart 或原生实现。
处理策略很朴素:剥离不可用的插件,或者自己补一个简化实现。比如项目里原本想用 shared_preferences 保存用户设置,在 OpenHarmony 上直接初始化失败,后来我改用文件读写自己封装了一个轻量级配置存储,十行代码的事,比折腾插件兼容性高效得多。如果确实需要某个复杂插件的完整能力,可以去查阅 OpenHarmony 社区维护的插件适配列表,有部分常用插件已经有人补了 ohos 实现。
还有个很隐蔽的坑,和 MediaCodecVideoRenderer 相关。我的 App 没有做任何音视频播放功能,但在某些设备上 Flutter 引擎初始化时的日志里会出现和媒体解码器相关的报错。排查之后确认这和业务无关,是引擎层尝试初始化硬件解码器失败后的降级日志,不影响应用正常运行。所以遇到不认识的非致命报错,先别慌,先看功能是否真的受影响,再决定要不要深挖。
6.3 编译产物清理与磁盘空间管理
OpenHarmony 工程的构建产物比普通 Flutter 工程更占磁盘,这是很多人忽略的事。build/ 目录、ohos/ 模块下的 build 目录、以及 .hvigor 临时目录,都可能积累大量构建中间文件。日常开发时如果磁盘告急,可以用 DevEco Studio 的 Build 菜单里的 Clean Project 来清理,这个动作不会影响源码和依赖配置。
补充一个进阶技巧:OpenHarmony 编译过程中会在工程目录生成 .hvigor 相关缓存,这个目录可以手动删除,下次构建会自动重建。如果你的工程里误提交了 build 目录或者 .hvigor 目录到 git,建议尽早加进 .gitignore,否则 git 仓库体积会迅速膨胀,团队成员每次拉取都要花很长时间。
6.4 设备连接和调试的细节问题
设备连接这块,我发现用开发板调试时最容易遇到的是 hdc 识别不到设备。排查思路和 adb 的套路一样:先确认设备的调试模式已经打开,再看 USB 线有没有接对端口,最后检查 hdc 服务是否正常。可以用 hdc kill 加 hdc start 重启服务试试,这个操作解决了我大约一半的“找不到设备”问题。
另一个实用命令是 hdc shell param get const.product.name,它可以从命令行快速确认设备产品型号。在排查问题的时候,明确自己跑在哪个硬件形态上非常重要,因为 RK3568 和 RK3588 的行为差异肉眼可见,很多性能问题和表现问题都跟设备档位强烈相关。
7. 几个值得沉淀的经验技巧
项目做到这个程度,有一些经验体会想单独拿出来聊聊,它们不一定能直接写成步骤化的教程,但都是实操中切实影响过我的判断。
第一点,如果想复刻这个项目,我建议先把学习日历做了,再做训练模块。日历模块是整个 App 的数据底座,它定义了数据库表结构、统计口径、日期边界规则,而这些规则会反过来影响训练记录怎么写入。我先做的是训练模块,结果写日历的时候发现训练记录的日期字段格式没定义好,只好回头改数据层,白白多花了一天时间。
第二点,OpenHarmony 的 Flutter 适配目前仍属于“快速演进期”,代码写完后一定要在真机上跑一段时间再下结论。模拟器上正常编译的工程,真机上可能因为签名、权限、硬件解码等差异暴露各种问题。我建议你拿到设备后,先写一个最小可用的 Hello World 工程,把签名、安装、日志输出整条链路跑通,再开始搬业务代码。这条链路不通,后面所有调试都是在沙地里盖楼。
第三点,主流的 Flutter 三方生态并不等于 OpenHarmony 三方生态。在 pub.dev 上找一个包前,先确认它是否存在平台插件依赖;如果有,再确认它的 ohos 实现是否有人维护。能只用 Dart 实现的逻辑,不要轻易引入平台插件,这个原则在这个生态阶段会保护你少踩很多坑。
这个项目从零到一跑通,我最大的体会是:OpenHarmony 的 Flutter 开发没有想象中那么可怕,但也绝对不比常规 Flutter 开发省心。它更像是在一个“新的平台”上做“旧框架”的适配,思路要跨端,但细节处处是平台特有的坑。如果你正准备入这个坑,希望这篇内容能帮你把前面最泥泞的一段路提前趟平。
