资讯动态

用Flutter开发OpenHarmony艺考题库App:从环境搭建到学习报告

发布时间:2026/10/20 18:52:11 来源:尧图企业网站定制
今年年初我接到一个需求要在 OpenHarmony 设备上做一款艺考真题题库App刷题、模考、错题本都要有最关键的是要带学习报告功能。团队里没人碰过 Flutter for OpenHarmony 这套组合网上能搜到的资料要么停留在跑通 Hello World要么就是官方文档里语焉不详的适配说明几乎没有一篇能告诉我“题库 统计报告”这种完整产品怎么落地。做完之后我觉得很有必要把全过程写下来——从环境搭建、数据模型设计到学习报告的统计口径和图表实现再到跨端适配时踩的一堆坑给后面想用 Flutter 做 OpenHarmony 应用的同行留一份能直接抄作业的参考。这篇文章适合有 Flutter 基础、想拓到 OpenHarmony 平台的客户端开发者也适合正在评估“题库类产品跨端方案”的产品和技术负责人。1. 为什么选 Flutter 做 OpenHarmony 题库需求背后的真实原因1.1 艺考刷题这个场景到底特殊在哪艺考和普通升学考不一样它不是一个“科目大盘子”而是覆盖美术、音乐、舞蹈、播音编导等多个大类每个大类下面还有细分方向的题库需求。我做这个项目时甲方是某培训机构他们的学生要刷的理论题主要是文艺常识、美术史、乐理、艺术概论、影视编导基础这几块。题型以单选题、多选题、判断题为主偶尔掺杂一些填空题。这带来三个特殊点知识点标签是刚需。学生刷完题不是只看对错的教务老师要能看出“这个班文艺常识普遍掌握得差”还是“乐理里面调式判断总是错”。所以题目数据模型里必须有 category 和 knowledgePoint 两个字段学习报告全靠它们聚合。没有这两个字段后面做报告就是空中楼阁。碎片化刷题场景多。艺考生的训练时间被素描、形体、发声练习切得很碎刷题基本发生在通勤路上、候场间隙、睡前十分钟离线可用是第一优先级在线同步反而是其次。报告要给机构看。学习报告不只是学生自己的复盘工具也是机构续课和教学调整的抓手。这意味着报告不能只给一个总正确率得拆到知识点维度最好还有时间趋势。1.2 Flutter OpenHarmony 的组合逻辑这个项目的客户端预算是有限的团队里没有专职的 ArkTS 原生开发。而 OpenHarmony 生态的设备尤其是机构会统一采购的学习终端和平板数量在明显增长产品覆盖范围把 OpenHarmony 划进来了。Flutter 在这里的价值是一套 Dart 代码同时出 Android 和 OpenHarmony 两个安装包。OpenHarmony 的 Flutter 支持来自开源社区的维护分支纯 Dart 实现的 UI 和业务逻辑基本都能被这层适配接住前提是别用太多平台插件。我在技术选型阶段就定了一条原则能直接搜到 OHOS 适配的插件才用搜不到就用纯 Dart 方案或者自己写 MethodChannel绝对不在关键路径上赌一个“据说能用”的插件。这条原则后面帮我们省了非常多排查时间。2. 环境搭建从 0 到 1版本选型和第一个 HAP2.1 工具链和版本选型Flutter for OpenHarmony 的 SDK 不能用官方 Flutter 发行版得用 OpenHarmony 社区维护的 flutter_flutter 仓库的 ohos 分支。我落地时用的是基于 Flutter 3.7 系列的适配版本这套 API 对做题库App完全够用。需要注意不要混用官方 Flutter SDK否则 build 时根本找不到 ohos 平台。需要配套的 OpenHarmony SDK在 IDE 里下载对应 API 版本的 SDK。构建产物是 HAP 包类似 Android 的 APK。调试期用 Debug 证书没问题发布必须配置签名这和 Android 的 keystore 逻辑类似。工具链清单大致如下组件作用备注flutter_flutterohos分支Dart 侧 SDK 与编译工具通过 git 拉取配置到 PATHOpenHarmony SDK原生侧编译所需在 IDE 中配置路径OpenHarmony 配套 IDE管理 ohos 工程、签名、构建 HAP打开项目下的 ohos 目录使用ohpmOpenHarmony 的包管理器原生侧依赖安装2.2 工程结构长什么样Flutter 工程创建之后除了常规的 android、ios 目录还会多出一个 ohos 目录这就是 OpenHarmony 的壳工程。核心结构是项目根/ ├─ lib/ # Dart 业务代码 ├─ ohos/ # OpenHarmony 壳工程 │ ├─ entry/ │ │ └─ src/main/ets/ # ArkTS 原生侧代码 │ ├─ AppScope/ # 应用级配置 │ └─ oh-package.json5 # 原生依赖声明 ├─ assets/ # 题目 JSON、图片等资源 └─ pubspec.yaml大多数时候我们只需要在 lib/ 下写 Dartohos/ 目录里的东西不做大改。只有涉及原生能力调用时才需要动 ets 目录下的文件。2.3 跑通第一个 HAP我这边跑通工程的完整路径是这样把 ohos 分支的 Flutter SDK 下载到本地配好 PATH确认flutter --version能输出版本信息。创建工程后在项目根目录新建local.properties写入ohos.sdk.dir你的OpenHarmony SDK路径。这一步不写后面flutter devices识别不到 OpenHarmony 设备。连接设备或启动模拟器执行flutter devices能看到带 ohos 标识的设备条目。直接flutter run -d 设备ID首次构建会非常慢因为要编译引擎相关的 so 文件耐心等。如果要出可安装的 HAP 包把 ohos 目录导入配套 IDEIDE 会跑 ohpm 安装原生依赖同时帮你处理签名再触发构建。2.4 环境上踩过的几个坑flutter doctor 一直报警。OpenHarmony 平台的工具链不会被完整识别doctor 会反复提示 Android toolchain 缺失。别慌这说明不了什么只看实际构建能不能过。Debug 能跑、Release 装不上。Release 包没配签名设备上安装直接失败。解决方式是 IDE 里生成证书并配置到工程的 build-profile.json5 里。热重载是半个废的。纯 Dart 代码改动基本能热更新但动了原生 ArkTS 或者引擎相关的东西必须整个 HAP 重新构建不能指望热重载救场。3. 题库和刷题核心数据模型、组卷与错题归集3.1 题目数据模型题目数据我设计成了一份 JSON 文件放进 assets而不是一开始就塞进数据库。原因有两个题目是只读数据一套题库撑死几千条全量加载进内存也就几兆用 JSON 启动时加载能绕开 sqflite 这类数据库插件在 OpenHarmony 上适配不稳定的问题。单题结构如下{ id: mc_00123, type: 0, category: 美术史, knowledgePoint: 文艺复兴美术, stem: 下列哪位艺术家被认为是“文艺复兴美术三杰”之一, options: [达·芬奇, 鲁本斯, 伦勃朗, 委拉斯贵兹], answer: [0], analysis: 文艺复兴美术三杰指达·芬奇、米开朗琪罗、拉斐尔。 }type 用 0 单选、1 多选、2 判断answer 用数组是为了兼容多选题。knowledgePoint 字段在学习报告阶段是聚合维度所以导入时就必须按一套固定知识点词表清洗不能随性乱填。这里的教训是数据清洗比写代码费时间。我们当时拿到的原始题库是 Excel里面“文艺复兴”一会儿写成“文艺复兴时期美术”一会儿写成“文艺复兴艺术”我写了段 Python 脚本按映射表统一替换才保证了后面报告统计口径不出岔子。3.2 刷题流程与错题归集刷题页的状态机很简单idle - answering - answered - next。用户点选项时记录选中答案点“下一题”时把这条答题记录写入本地库同时更新当前会话的统计缓存。练习记录表是整套报告系统的地基字段是这样设计的CREATE TABLE practice_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id TEXT NOT NULL, category TEXT NOT NULL, knowledge_point TEXT NOT NULL, is_correct INTEGER NOT NULL, selected_answer TEXT NOT NULL, duration_ms INTEGER NOT NULL, created_at INTEGER NOT NULL ); CREATE INDEX idx_records_created ON practice_records(created_at); CREATE INDEX idx_records_kp ON practice_records(knowledge_point);每条记录必须冗余 category 和 knowledge_point而不是通过 question_id 去题目表关联查。原因很实际题目 JSON 是内存数据如果报告聚合时要回表关联既要跨存储又慢。冗余字段虽然在插入时多写几个字符串但换来的是报告查询一次本地库就全部拿到。错题归集我用了单独一张错题表question_id 做唯一约束答错自动插入答对且已在错题表里的记录删掉。这样“错题清零”功能也顺手实现了。3.3 模拟考试组卷逻辑模考不是随机抽题那么简单得按考纲比例来。我用的组卷算法是按 category 的权重分层抽样再做全局洗牌最后用固定随机种子保证同一套卷子在不同设备上结果一致。ListQuestion buildPaper(ListQuestion pool, MapString, double blueprint, int seed) { final random Random(seed); final result Question[]; blueprint.forEach((category, weight) { final bucket pool.where((q) q.category category).toList()..shuffle(random); final need (weight * totalCount).round(); result.addAll(bucket.take(need)); }); result.shuffle(random); return result; }交卷逻辑有个坑未做答的题在评分时不能漏掉要按错误处理。模考记录也会写入 practice_records但单独加一个 source 标识字段区分练习和模考报告里这两个数据要分开看混在一起会严重污染知识点正确率。4. 学习报告实现从埋点到图表4.1 数据采集一次答题记录什么学习报告能不能做出来全看埋点定义。我埋的字段就是 3.2 节那张表question_id、category、knowledge_point、is_correct、duration_ms、created_at。这里要说三个容易漏的点duration_ms 必须逐题记。有了它才能算“平均每题用时”“知识点耗时”这些指标对机构判断学生熟练度很有价值。created_at 用毫秒时间戳而不是存成“2025-04-01 10:00”这种格式化字符串。日期聚合只有在查询时才做格式化存时间戳能避免时区问题和格式解析开销。会话时长单独处理。单题时长的总和并不等于用户实际学习时长因为用户会在两道题之间发呆。我在刷题会话开始和结束时各记一个时间点App 切后台时用 AppLifecycleState 回调自动结算本段这个口径更接近真实学习投入。4.2 统计口径正确率、学习时长、连续天数怎么算统计口径是最容易被产品和技术拉扯的地方我踩过之后强烈建议先写一份统计口径文档再写代码。我最终落地的口径是这样的正确率 正确题数 ÷ 总题数。但模考中未作答的题一律计错练习中用户中途放弃的不计。知识点掌握度 该知识点答对次数 ÷ 该知识点练习次数。练习题量少于 3 条的知识点不展示避免“练了一题对了就说掌握度 100%”的假象。学习时长 会话时长累加不是单题时长累加。连续学习天数 从今天开始往前扫只要当天练习记录数大于 0 就算连续如果今天还没有记录则从昨天开始扫。这样不会因为“今天还没开始学”就粗暴断掉连续记录。聚合查询用 SQL 做也行用 Dart 内存聚合也行。我这边因为已经全量查了 records直接在内存里分组更顺手MapString, double knowledgeAccuracy(ListPracticeRecord records) { final stat String, Listint{}; for (final r in records) { final s stat.putIfAbsent(r.knowledgePoint, () [0, 0]); s[1]; if (r.isCorrect 1) s[0]; } return stat.map((kp, s) MapEntry(kp, s[0] / s[1])); }4.3 图表落地fl_chart 折线与雷达图学习报告页我做了四个可视化块7日正确率趋势折线图、知识点掌握度雷达图、薄弱知识点 Top5列表、每日刷题量柱状图。图表库选了 fl_chart它在 OpenHarmony 上的表现让我意外地放心因为它是纯 Dart 绘制不依赖原生画布能力。折线图用 LineChart雷达图用 RadarChartRadarChart( RadarChartData( dataSets: [ RadarDataSet( dataEntries: entries, borderColor: Colors.blue, fillColor: Colors.blue.withOpacity(0.2), ), ], radarShape: RadarShape.polygon, ticksTextStyle: const TextStyle(fontSize: 10), ), )这里有个真实的坑要讲RadarChart 在部分 OpenHarmony 设备上对触摸事件处理有兼容问题如果用户手指滑过雷达图偶尔会闪退。我没花时间深究底层直接把整个报告的图表区域包了一层 IgnorePointer用户只能看不能点。报告页本来就是展示型页面交互需求为零这个取舍非常划算。4.4 报告缓存与刷新策略报告页打开时如果每次都全量扫 practice_records数据量上来后会卡。我加了一张冗余聚合表 daily_statsCREATE TABLE daily_stats ( date TEXT PRIMARY KEY, total_count INTEGER DEFAULT 0, correct_count INTEGER DEFAULT 0, duration_ms INTEGER DEFAULT 0 );每次答题完成就在事务里更新当天这一行。打开报告页时只需要查daily_stats 算今日数据和 7 日趋势practice_records 按 knowledge_point 分组算掌握度。两次小查询毫秒级响应。如果用户通过某些后台手段清了练习记录daily_stats 会和 records 不一致。我在设置页放了一个“重建统计数据”入口提供全量重建方法报给用户“修复中”省了很多客诉。5. 跨端实坑清单OpenHarmony 上插件与原生协作5.1 插件兼容地图这个项目做完我对 Flutter 插件在 OpenHarmony 上的兼容性总结了一张表给同行做个参考插件/库是否纯 DartOpenHarmony 可用性我的处理provider是正常直接用fl_chart是正常直接用关闭触摸shared_preferences否有社区适配版版本需匹配找个固定版本锁死path_provider否没有可用的自写 MethodChannelsqflite否不可用改用 JSON 文件存储注意插件兼容版本是动态的今天能用的明天可能因为 SDK 升级挂掉。我的建议是锁定 flutter_flutter 分支的 commit锁定所有依赖的精确版本绝不轻易升级。这个项目从联调到交付工具链一个没升就是因为稳定压倒一切。5.2 自写 MethodChannel 对接 ArkTSpath_provider 用不了之后我写了一个极简的 MethodChannel 拿应用文件目录。Dart 侧代码static const MethodChannel _channel MethodChannel(app/native); FutureString getAppDir() async { return await _channel.invokeMethod(getAppDir); }原生侧是用 ArkTS 实现的和 Android 上用 Kotlin 写插件是两套完全不同的 API。我这边只是在壳工程的 ArkTS 入口里注册了 channel 实现返回文件系统路径。刚开始不熟悉 ArkTS 的模块注册规则折腾了一天才跑通原因只是我在错误的生命周期节点执行了注册。后来我把这一小段原生代码写成注释文档放在工程里交给团队其他人就没再出过问题。方法论上我想多说一句自写 MethodChannel 的数量要克制。每多一个 channel就多一个需要在原生侧验证的边界。这个项目把所有原生依赖收敛到了三个 channel应用目录获取、轻量 KV 存储、设备信息。剩下的全靠纯 Dart 解决。5.3 包体积和性能调优HAP 发布包体积一开始很大主要成本在 Flutter 引擎的 so 文件。我做三件事把它压了下来发布构建加--obfuscate --split-debug-info混淆的同时剥离符号表体积减小明显。assets 里只打包当前大纲对应考点的题目 JSON不把全量题库历史版本打进去。题目配图统一压缩到 WebP单张控制在 100KB 以内。性能上最大的体感差异是 Debug 模式明显比 Android 卡上 Release 包之后流畅度恢复正常。题库列表页我用了 itemExtent 固定行高避免长列表滚动时的反复测量开销。这些在 Android 上可能无感的优化在 OpenHarmony 的 Flutter 适配层上就是实打实的流畅度差距。6. 交付之后回头看这个项目真正的难点不在代码6.1 时间花在了三个没写代码的地方回头看整个交付最花时间的三件事都不是 Flutter 代码本身第一是题目 Excel 清洗成标准 JSON几百道题的人工核对第二是学习报告统计口径的确认产品和教务来回拉扯了好几轮第三是插件兼容性验证每个依赖都要在真机上试一遍。我自己的感受是Flutter for OpenHarmony 的 UI 层开发体验已经相当够用真正的不确定性全在生态层。哪个插件没适配、哪个原生 API 换了签名这些问题不会出现在报错信息里而是冷不丁在某个流程上绊你一下。6.2 给后来者的三个选型标准如果你也想入坑 Flutter for OpenHarmony我建议你选依赖时用三条标准过滤纯 Dart 优先能查到官方维护的 OHOS 适配记录才考虑原生插件。API 简单且可替换万一插件挂了自己写 MethodChannel 的成本要可控。版本锁死不要追新。这套组合下新版本带来的不确定远大于收益。6.3 最后一点体会做完这套题库如果让我重来一次我会在第一天就把“学习报告口径文档”和“依赖兼容矩阵”两个东西立起来而不是边开发边补。艺考题库本身不难难的是它要求你在一个适配还不成熟的平台上把数据、统计、可视化完整跑通。但反过来说正因为平台生态不成熟先趟过这条路的人后面再遇到同类的项目基本就是降维打击了。希望这篇分享能帮你少踩几个我踩过的坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑