鸿蒙PC迁移:Anki Qt 记忆卡片工具鸿蒙PC适配全记录
一、写在前面
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区:https://harmonypc.csdn.net/
项目开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_Anki
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
这篇文章记录的是 Anki 在 HarmonyOS PC / OpenHarmony PC 环境中的一次 Qt 路线适配过程。
Anki 是一个很经典的间隔重复记忆卡片软件,很多人用它背单词、复习知识点、维护自己的卡片库。和一些单层桌面应用不同,Anki 的原工程不是一个简单的 Qt Widgets 或 Qt Quick 项目,它的桌面版本由多层组成:
qt/aqt/:桌面 GUI 层,主要是 Python + PyQt。ts/:Svelte / TypeScript 写的 Web 前端组件。pylib/:Python 业务库,并通过 bridge 调用底层能力。rslib/:Rust 核心层,负责集合、调度、搜索、同步等核心逻辑。proto/:跨语言通信使用的 Protobuf 接口定义。
所以这次适配的重点不是把一个网页打进 HAP,也不是把原桌面所有 Python / Rust / WebView 能力一次性完整搬到鸿蒙 PC,而是先走一条可验证、可运行、可继续扩展的 Qt for HarmonyOS 路线:
- 新建
harmony_pc/鸿蒙工程壳。 - ArkTS 侧只负责 Stage Ability、窗口、Qt QPA 启动。
- Native 侧生成
libentry.so,用 Qt Quick / QML 承载界面。 - C++ 侧实现一个轻量本地
AnkiStore,先完成卡组、卡片、学习、导入、导出、备份等本地可用闭环。 - 后续再逐步替换本地 JSON 后端,接入 Anki 原有 Rust / Python 核心能力。
最终效果是:鸿蒙 PC 上可以安装并启动 HAP,应用以 Qt Quick 界面运行,支持中文界面、本地卡组、本地卡片、学习流程、卡片管理、文本导入、卡组导出、集合备份、窗口缩放适配和长页面滚动。

二、项目背景:Anki 原工程不是单层 Qt 应用
拿到 Anki 工程后,首先要判断它到底是什么技术栈。Anki 的目录结构比较能说明问题:
anki-main/
├── qt/
│ └── aqt/ # PyQt 桌面 GUI
├── ts/ # Svelte / TypeScript Web 组件
├── pylib/ # Python 业务库
├── rslib/ # Rust 核心层
├── proto/ # Protobuf 接口定义
├── ftl/ # 多语言翻译资源
├── justfile # 构建、测试、运行入口
└── harmony_pc/ # 本次新增的鸿蒙 PC 适配工程
这意味着它不是“直接把一个 Qt main.cpp 交给鸿蒙 CMake 编译”这么简单。原桌面 Anki 依赖 Python 运行时、PyQt、Rust 动态库、WebView、TypeScript 生成物和 Anki 自己的一套跨语言 bridge。如果第一步就追求完整移植,问题会同时集中在 Python 运行时、Rust ABI、Qt WebEngine/WebView、文件系统、插件体系、同步和打包体积上,调试面会非常大。
因此本次采用分阶段策略:
- 第一阶段:证明 Qt for HarmonyOS 能稳定承载 Anki 风格的桌面 UI,并做出本地可用的学习闭环。
- 第二阶段:把本地 JSON 数据层逐步替换为真正的 Anki Core / Rust bridge。
- 第三阶段:补齐 apkg / colpkg、媒体资源、模板渲染、FSRS 调度、同步和插件等能力。
这篇文章记录的是第一阶段:先把应用跑起来,并做到用户可以在鸿蒙 PC 上真实创建卡组、添加卡片、学习复习、管理数据。

三、适配路线:选择 Qt for HarmonyOS,而不是 ArkUI 重写
一开始也可以选择 ArkUI 重写,但对 Anki 这种桌面工具来说,直接重写会遇到两个问题:
- 原桌面 UI 和业务能力很难复用,等于重新做一个 Anki。
- 后续要接回原 Anki Core 时,ArkUI 页面、Python/PyQt 逻辑、Rust bridge 之间会出现新的割裂。
因此这次选择 Qt for HarmonyOS 路线。这个思路和一些 Qt 桌面应用的鸿蒙 PC 适配类似:保留 Qt 作为 UI 承载层,鸿蒙工程只做启动壳和平台适配。这里参考了 MoonPlayer Qt 适配鸿蒙 PC 的工程组织方式,但没有照搬 MoonPlayer 的播放器业务代码,Anki 这边重新实现了自己的 QML 界面和本地数据层。
本次新增的鸿蒙工程主要放在:
anki-main/harmony_pc/
├── AppScope/
│ └── app.json5
├── build-profile.json5
├── entry/
│ ├── build-profile.json5
│ ├── libs/arm64-v8a/
│ │ └── libplugins_platforms_qopenharmony.so
│ └── src/main/
│ ├── cpp/
│ │ ├── CMakeLists.txt
│ │ ├── Main.qml
│ │ ├── anki_ohos_stub.cpp
│ │ ├── anki_store.cpp
│ │ ├── anki_store.h
│ │ └── anki_ohos_resources.qrc
│ ├── ets/
│ │ ├── entryability/EntryAbility.ets
│ │ └── pages/Index.ets
│ └── module.json5
├── hvigor/
├── oh-package.json5
└── qtforharmony_sdk/
整体启动链路可以理解为:
EntryAbility.ets是鸿蒙 Stage 模型入口。Index.ets提供 Qt QPA 需要的页面承载点。libplugins_platforms_qopenharmony.so负责 Qt OpenHarmony QPA 对接。libentry.so是本项目 native 入口。anki_ohos_stub.cpp创建QGuiApplication和QQmlApplicationEngine。Main.qml渲染 Anki 鸿蒙 PC 版界面。AnkiStore通过 Qt 对象暴露给 QML,负责本地数据读写和学习调度。
四、搭建鸿蒙 Qt 启动链路
Qt for HarmonyOS 适配里最关键的一步,是让 HAP 里的 native library 被鸿蒙 Ability 正确启动。
EntryAbility.ets 中引入 QPA 插件:
import qpa from 'libplugins_platforms_qopenharmony.so';
窗口创建后先加载 ArkTS 页面:
await windowStage.loadContent(this.loadContentUrl, localStore);
然后把鸿蒙窗口交给 Qt QPA,并启动 Qt 应用:
qpa.handleJsTopWindowCreated(this.name, this);
qpa.startQtApplication(this);
EntryAbility.ets 里还保留了 file:// / content:// URI 缓存逻辑。这个能力当前主要是为后续从系统文件选择器打开 Anki 数据包、备份文件、导入文件做准备:鸿蒙侧拿到外部 URI 后,先复制到应用 cache 目录,再把可访问的本地路径传给 Qt。
Native 入口在 anki_ohos_stub.cpp:
QGuiApplication app(argc, argv);
AnkiStore store;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("ankiStore"), &store);
engine.load(QUrl(QStringLiteral("qrc:/anki/harmony/Main.qml")));
return app.exec();
这里没有直接启动原桌面 Anki 的 PyQt 入口,而是先启动一个面向鸿蒙 PC 的 Qt Quick 应用。QML 侧通过 ankiStore 调用 C++ 本地数据层。
CMake 侧主要处理三件事:
- 自动定位 Qt for HarmonyOS SDK。
- 链接 Qt Core / Gui / Qml / Quick / Network / Svg / Widgets 等模块。
- 打包 Qt QML 模块和 QML plugin,避免运行时白屏。
其中 QT_PREFIX 一开始是最容易出错的地方。构建时如果找不到:
lib/cmake/Qt5/Qt5Config.cmake
就会出现类似错误:
QT_PREFIX must point to a Qt 5 for HarmonyOS SDK with lib/cmake/Qt5/Qt5Config.cmake
为了解决这个问题,CMake 做了多路查找:
set(ANKI_QT_PREFIX_CANDIDATES)
if (QT_PREFIX)
...
endif()
if (DEFINED ENV{QT_FOR_HARMONY_PREFIX})
...
endif()
list(APPEND ANKI_QT_PREFIX_CANDIDATES
"${HARMONY_PROJECT_ROOT}/qtforharmony_sdk"
"${WORKSPACE_ROOT}/ok-moonplayer-develop/moonplayer-ohos/harmony_pc/qtforharmony_sdk"
)
也就是说,本项目优先使用自己的 harmony_pc/qtforharmony_sdk,本地迭代时也允许临时复用旁边 MoonPlayer 项目的 Qt for Harmony SDK。这样做只是为了复用本机已经验证过的 Qt SDK 路径,不复制 MoonPlayer 的业务逻辑。
另外,白屏问题也和 QML 运行时模块有关。Main.qml 虽然是我们自己的文件,但 Qt Quick 本身还依赖 QtQml、QtQuick.2、QtQuick/Window.2 等模块。CMake 中将这些模块写入生成的 qrc,并把对应 .so plugin 复制到 native 输出目录:
set(ANKI_QT_QML_MODULE_ROOTS
QtQml
QtQuick.2
QtQuick/Window.2
)

五、从白屏到可用:先做本地 Anki MVP
第一版跑起来后,问题并不是一下就结束了。实际在鸿蒙 PC 设备上验证时,经历了几个典型问题:
- 启动白屏:Qt QML 模块或 plugin 没有被正确打包。
- 启动闪退:native 入口、QPA 绑定或依赖库路径不完整。
- 页面能显示但点击无反应:QML 交互层和布局覆盖关系需要重新检查。
- 窗口放大后,内容没有同步放大:原始固定尺寸 UI 不适配鸿蒙 PC 窗口。
- 页面内容显示不全,鼠标滚轮不能滚动:长页面没有显式滚动容器高度。
- 管理页“导入卡片”按钮点击无反应:按钮所在区域被后续备份区域覆盖,视觉上看得到,实际点击命中了上层布局。
这些问题说明,桌面工具上鸿蒙 PC 后,不能只看“画面出现了没有”,还必须逐项验证窗口缩放、滚轮、鼠标点击、长页面、输入框、弹窗和数据写入。
本次第一阶段没有直接接入完整 Anki 后端,而是实现了一个 C++ / Qt 本地数据层 AnkiStore。它负责把数据保存到应用沙箱 JSON 文件:
/data/app/el2/100/base/net.ankiweb.anki/files/anki_harmony_collection.json
当前本地 MVP 支持:
- 卡组列表、卡组新增、卡组重命名、卡组删除。
- 卡片新增、编辑、删除、搜索。
- 学习页显示问题、显示答案、选择“重来 / 困难 / 良好 / 简单”。
- 简化调度逻辑,根据回答结果更新到期时间、间隔和复习次数。
- 文本导入卡片。
- 当前卡组文本导出。
- 集合 JSON 导入 / 导出 / 备份。
- 中文 / 英文界面切换。
学习调度是一个简化版本,目标是先证明学习闭环成立:
Again:当天继续复习,并降低 ease。Hard:下一天复习。Good:按当前 interval 增长。Easy:更快增长 interval。
UI 侧主要集中在 Main.qml。为了适配鸿蒙 PC 窗口,做了几个关键处理:
- 使用设计宽高和
uiScale,让窗口放大时内容同步放大。 - 长页面使用显式
contentHeight,避免 Flickable 判断不到可滚动区域。 - 管理页和浏览页增加右侧“上 / 下”滚动按钮,方便在鼠标滚轮不稳定时也能滚动。
- 管理页重新调整导入、导出、备份区域高度,避免按钮被后续区域覆盖。
- 页面底部导航、主要按钮、输入框、列表项都使用较大的点击区域,更适合 PC 鼠标操作。
其中“导入卡片点击无反应”的问题比较典型。表面看按钮在界面上,但由于页面布局高度不足,下面的备份区域实际覆盖到了导入区域上方,导致鼠标点击命中了覆盖层。修复方式不是只改按钮事件,而是重新梳理管理页内容高度、区块间距和 Flickable 的 contentHeight,让每个可点击控件都处在真实可交互区域内。

六、学习闭环验证:添加、浏览、学习、写回
一个记忆卡片工具是否真的可用,不能只看首页,而要验证完整闭环:
- 创建或选择一个卡组。
- 在“新增”页添加问题和答案。
- 在“浏览”页能搜索、编辑、删除卡片。
- 在“学习”页能看到问题。
- 点击“显示答案”后出现答案和评分按钮。
- 点击“良好”等评分按钮后,卡片状态写回本地 JSON。
- 关闭并重新打开应用后,数据仍然存在。
本次在设备上验证过学习流程:点击“显示答案”后,可以看到答案区域和四个评分按钮;点击“良好”后,卡片的 reps、interval、dueDay 等字段会更新,并写入:
/data/app/el2/100/base/net.ankiweb.anki/files/anki_harmony_collection.json
可以用下面命令把设备上的集合文件拉回本机检查:
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc file recv \
/data/app/el2/100/base/net.ankiweb.anki/files/anki_harmony_collection.json \
~/XM/anki-main/harmony_pc/anki_harmony_collection_device.json
安装和启动命令如下:
cd ~/XM/anki-main
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc install -r harmony_pc/entry/build/default/outputs/default/entry-default-signed.hap
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell aa force-stop net.ankiweb.anki
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell aa start -b net.ankiweb.anki -a EntryAbility

七、当前完成度与后续方向
到这里,Anki 鸿蒙 PC Qt 适配已经完成了第一阶段目标:应用能作为 HAP 安装运行,并且用户可以在鸿蒙 PC 上完成本地记忆卡片的基础使用。
当前已经完成:
harmony_pc/鸿蒙 PC 工程壳。- ArkTS Stage Ability + Qt QPA 启动链路。
libentry.sonative 入口。- Qt Quick / QML 主界面。
- C++ / Qt 本地
AnkiStore数据层。 - 本地 JSON 持久化。
- 卡组、卡片、学习、浏览、管理。
- 文本导入、卡组导出、集合备份。
- 中文 / 英文界面切换。
- 鸿蒙 PC 窗口缩放适配。
- 长页面滚动和管理页点击问题修复。
这次适配最大的经验是:复杂桌面软件迁移到鸿蒙 PC,不一定要一开始就重写所有功能。对 Qt 项目,尤其是有桌面工具属性的项目,可以先把 Qt for HarmonyOS 启动链路打通,再把核心使用路径做成本地可用 MVP。这样可以尽早在真实设备上发现白屏、闪退、缩放、滚动、点击覆盖、输入等问题,也能让后续接入原核心能力时有一个稳定的 UI 和工程基础。
对于 Anki 这类软件来说,第一阶段最重要的不是“看起来像”,而是“真的能学一张卡、写回一条复习记录”。只要这个闭环成立,后续再逐步把本地 MVP 替换成完整 Anki Core,就有了比较清晰的演进路线。
更多推荐

所有评论(0)