鸿蒙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 项目,它的桌面版本由多层组成:

  1. qt/aqt/:桌面 GUI 层,主要是 Python + PyQt。
  2. ts/:Svelte / TypeScript 写的 Web 前端组件。
  3. pylib/:Python 业务库,并通过 bridge 调用底层能力。
  4. rslib/:Rust 核心层,负责集合、调度、搜索、同步等核心逻辑。
  5. proto/:跨语言通信使用的 Protobuf 接口定义。

所以这次适配的重点不是把一个网页打进 HAP,也不是把原桌面所有 Python / Rust / WebView 能力一次性完整搬到鸿蒙 PC,而是先走一条可验证、可运行、可继续扩展的 Qt for HarmonyOS 路线:

  1. 新建 harmony_pc/ 鸿蒙工程壳。
  2. ArkTS 侧只负责 Stage Ability、窗口、Qt QPA 启动。
  3. Native 侧生成 libentry.so,用 Qt Quick / QML 承载界面。
  4. C++ 侧实现一个轻量本地 AnkiStore,先完成卡组、卡片、学习、导入、导出、备份等本地可用闭环。
  5. 后续再逐步替换本地 JSON 后端,接入 Anki 原有 Rust / Python 核心能力。

最终效果是:鸿蒙 PC 上可以安装并启动 HAP,应用以 Qt Quick 界面运行,支持中文界面、本地卡组、本地卡片、学习流程、卡片管理、文本导入、卡组导出、集合备份、窗口缩放适配和长页面滚动。

二、项目背景:Anki 原工程不是单层 Qt 应用

拿到 Anki 工程后,首先要判断它到底是什么技术栈。Anki 的目录结构比较能说明问题:

text 复制代码
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、文件系统、插件体系、同步和打包体积上,调试面会非常大。

因此本次采用分阶段策略:

  1. 第一阶段:证明 Qt for HarmonyOS 能稳定承载 Anki 风格的桌面 UI,并做出本地可用的学习闭环。
  2. 第二阶段:把本地 JSON 数据层逐步替换为真正的 Anki Core / Rust bridge。
  3. 第三阶段:补齐 apkg / colpkg、媒体资源、模板渲染、FSRS 调度、同步和插件等能力。

这篇文章记录的是第一阶段:先把应用跑起来,并做到用户可以在鸿蒙 PC 上真实创建卡组、添加卡片、学习复习、管理数据。

三、适配路线:选择 Qt for HarmonyOS,而不是 ArkUI 重写

一开始也可以选择 ArkUI 重写,但对 Anki 这种桌面工具来说,直接重写会遇到两个问题:

  1. 原桌面 UI 和业务能力很难复用,等于重新做一个 Anki。
  2. 后续要接回原 Anki Core 时,ArkUI 页面、Python/PyQt 逻辑、Rust bridge 之间会出现新的割裂。

因此这次选择 Qt for HarmonyOS 路线。这个思路和一些 Qt 桌面应用的鸿蒙 PC 适配类似:保留 Qt 作为 UI 承载层,鸿蒙工程只做启动壳和平台适配。这里参考了 MoonPlayer Qt 适配鸿蒙 PC 的工程组织方式,但没有照搬 MoonPlayer 的播放器业务代码,Anki 这边重新实现了自己的 QML 界面和本地数据层。

本次新增的鸿蒙工程主要放在:

text 复制代码
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/

整体启动链路可以理解为:

  1. EntryAbility.ets 是鸿蒙 Stage 模型入口。
  2. Index.ets 提供 Qt QPA 需要的页面承载点。
  3. libplugins_platforms_qopenharmony.so 负责 Qt OpenHarmony QPA 对接。
  4. libentry.so 是本项目 native 入口。
  5. anki_ohos_stub.cpp 创建 QGuiApplication 和 QQmlApplicationEngine。
  6. Main.qml 渲染 Anki 鸿蒙 PC 版界面。
  7. AnkiStore 通过 Qt 对象暴露给 QML,负责本地数据读写和学习调度。

四、搭建鸿蒙 Qt 启动链路

Qt for HarmonyOS 适配里最关键的一步,是让 HAP 里的 native library 被鸿蒙 Ability 正确启动。

EntryAbility.ets 中引入 QPA 插件:

ts 复制代码
import qpa from 'libplugins_platforms_qopenharmony.so';

窗口创建后先加载 ArkTS 页面:

ts 复制代码
await windowStage.loadContent(this.loadContentUrl, localStore);

然后把鸿蒙窗口交给 Qt QPA,并启动 Qt 应用:

ts 复制代码
qpa.handleJsTopWindowCreated(this.name, this);
qpa.startQtApplication(this);

EntryAbility.ets 里还保留了 file:// / content:// URI 缓存逻辑。这个能力当前主要是为后续从系统文件选择器打开 Anki 数据包、备份文件、导入文件做准备:鸿蒙侧拿到外部 URI 后,先复制到应用 cache 目录,再把可访问的本地路径传给 Qt。

Native 入口在 anki_ohos_stub.cpp:

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 侧主要处理三件事:

  1. 自动定位 Qt for HarmonyOS SDK。
  2. 链接 Qt Core / Gui / Qml / Quick / Network / Svg / Widgets 等模块。
  3. 打包 Qt QML 模块和 QML plugin,避免运行时白屏。

其中 QT_PREFIX 一开始是最容易出错的地方。构建时如果找不到:

text 复制代码
lib/cmake/Qt5/Qt5Config.cmake

就会出现类似错误:

text 复制代码
QT_PREFIX must point to a Qt 5 for HarmonyOS SDK with lib/cmake/Qt5/Qt5Config.cmake

为了解决这个问题,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 输出目录:

cmake 复制代码
set(ANKI_QT_QML_MODULE_ROOTS
    QtQml
    QtQuick.2
    QtQuick/Window.2
)

五、从白屏到可用:先做本地 Anki MVP

第一版跑起来后,问题并不是一下就结束了。实际在鸿蒙 PC 设备上验证时,经历了几个典型问题:

  1. 启动白屏:Qt QML 模块或 plugin 没有被正确打包。
  2. 启动闪退:native 入口、QPA 绑定或依赖库路径不完整。
  3. 页面能显示但点击无反应:QML 交互层和布局覆盖关系需要重新检查。
  4. 窗口放大后,内容没有同步放大:原始固定尺寸 UI 不适配鸿蒙 PC 窗口。
  5. 页面内容显示不全,鼠标滚轮不能滚动:长页面没有显式滚动容器高度。
  6. 管理页"导入卡片"按钮点击无反应:按钮所在区域被后续备份区域覆盖,视觉上看得到,实际点击命中了上层布局。

这些问题说明,桌面工具上鸿蒙 PC 后,不能只看"画面出现了没有",还必须逐项验证窗口缩放、滚轮、鼠标点击、长页面、输入框、弹窗和数据写入。

本次第一阶段没有直接接入完整 Anki 后端,而是实现了一个 C++ / Qt 本地数据层 AnkiStore。它负责把数据保存到应用沙箱 JSON 文件:

text 复制代码
/data/app/el2/100/base/net.ankiweb.anki/files/anki_harmony_collection.json

当前本地 MVP 支持:

  1. 卡组列表、卡组新增、卡组重命名、卡组删除。
  2. 卡片新增、编辑、删除、搜索。
  3. 学习页显示问题、显示答案、选择"重来 / 困难 / 良好 / 简单"。
  4. 简化调度逻辑,根据回答结果更新到期时间、间隔和复习次数。
  5. 文本导入卡片。
  6. 当前卡组文本导出。
  7. 集合 JSON 导入 / 导出 / 备份。
  8. 中文 / 英文界面切换。

学习调度是一个简化版本,目标是先证明学习闭环成立:

  1. Again:当天继续复习,并降低 ease。
  2. Hard:下一天复习。
  3. Good:按当前 interval 增长。
  4. Easy:更快增长 interval。

UI 侧主要集中在 Main.qml。为了适配鸿蒙 PC 窗口,做了几个关键处理:

  1. 使用设计宽高和 uiScale,让窗口放大时内容同步放大。
  2. 长页面使用显式 contentHeight,避免 Flickable 判断不到可滚动区域。
  3. 管理页和浏览页增加右侧"上 / 下"滚动按钮,方便在鼠标滚轮不稳定时也能滚动。
  4. 管理页重新调整导入、导出、备份区域高度,避免按钮被后续区域覆盖。
  5. 页面底部导航、主要按钮、输入框、列表项都使用较大的点击区域,更适合 PC 鼠标操作。

其中"导入卡片点击无反应"的问题比较典型。表面看按钮在界面上,但由于页面布局高度不足,下面的备份区域实际覆盖到了导入区域上方,导致鼠标点击命中了覆盖层。修复方式不是只改按钮事件,而是重新梳理管理页内容高度、区块间距和 Flickable 的 contentHeight,让每个可点击控件都处在真实可交互区域内。

六、学习闭环验证:添加、浏览、学习、写回

一个记忆卡片工具是否真的可用,不能只看首页,而要验证完整闭环:

  1. 创建或选择一个卡组。
  2. 在"新增"页添加问题和答案。
  3. 在"浏览"页能搜索、编辑、删除卡片。
  4. 在"学习"页能看到问题。
  5. 点击"显示答案"后出现答案和评分按钮。
  6. 点击"良好"等评分按钮后,卡片状态写回本地 JSON。
  7. 关闭并重新打开应用后,数据仍然存在。

本次在设备上验证过学习流程:点击"显示答案"后,可以看到答案区域和四个评分按钮;点击"良好"后,卡片的 reps、interval、dueDay 等字段会更新,并写入:

text 复制代码
/data/app/el2/100/base/net.ankiweb.anki/files/anki_harmony_collection.json

可以用下面命令把设备上的集合文件拉回本机检查:

bash 复制代码
/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

安装和启动命令如下:

bash 复制代码
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 上完成本地记忆卡片的基础使用。

当前已经完成:

  1. harmony_pc/ 鸿蒙 PC 工程壳。
  2. ArkTS Stage Ability + Qt QPA 启动链路。
  3. libentry.so native 入口。
  4. Qt Quick / QML 主界面。
  5. C++ / Qt 本地 AnkiStore 数据层。
  6. 本地 JSON 持久化。
  7. 卡组、卡片、学习、浏览、管理。
  8. 文本导入、卡组导出、集合备份。
  9. 中文 / 英文界面切换。
  10. 鸿蒙 PC 窗口缩放适配。
  11. 长页面滚动和管理页点击问题修复。

这次适配最大的经验是:复杂桌面软件迁移到鸿蒙 PC,不一定要一开始就重写所有功能。对 Qt 项目,尤其是有桌面工具属性的项目,可以先把 Qt for HarmonyOS 启动链路打通,再把核心使用路径做成本地可用 MVP。这样可以尽早在真实设备上发现白屏、闪退、缩放、滚动、点击覆盖、输入等问题,也能让后续接入原核心能力时有一个稳定的 UI 和工程基础。

对于 Anki 这类软件来说,第一阶段最重要的不是"看起来像",而是"真的能学一张卡、写回一条复习记录"。只要这个闭环成立,后续再逐步把本地 MVP 替换成完整 Anki Core,就有了比较清晰的演进路线。

相关推荐
李游Leo19 分钟前
《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》05:Camera控制、手势映射与空间浏览交互【鸿蒙心迹】
3d·交互·harmonyos
沫璃染墨27 分钟前
《Qt从零入门系列(十一):Qt事件机制详解——从QEvent到鼠标、键盘与定时器事件》
c++·qt·ui·硬件工程·交互·个人开发·qt5
实心儿儿1 小时前
Qt — Qt 窗口
开发语言·qt
resh_people1 小时前
开源鸿蒙平台 KMP/CMP 三方库「Okio」适配全流程
华为·开源·harmonyos
李游Leo2 小时前
《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》08:动画、生命周期与SpaceRoom工程化收尾【鸿蒙心迹】
3d·harmonyos
-madongyu-2 小时前
HCIE-GaussDB V2.0 笔试考点总览:模块权重、核心要点与高频数字速记
数据库·华为·gaussdb
轻口味2 小时前
HarmonyOS 7 新特性2:音频编创——轻音台里的降噪、环绕与格式转换
华为·音视频·harmonyos·鸿蒙·音频编创
极客范儿3 小时前
华为HCIP网络工程师认证—DHCP、NAT和 PPPOE
网络·华为
m0_738185823 小时前
Flutter 鸿蒙化实战:flutter_blue_plus 适配 OpenHarmony,蓝牙扫描连接开箱即用
flutter·华为·harmonyos·鸿蒙
李游Leo3 小时前
HarmonyOS 7 + ArkUI + Adaptive Layout 学习笔记:折叠屏多形态布局适配与窗口状态响应机制【鸿蒙心迹】
笔记·学习·harmonyos