Air 系列应用交流/AirMath(from GeoGebra)|AirMath · 鸿蒙 PC 适配全记录

AirMath(from GeoGebra)|AirMath · 鸿蒙 PC 适配全记录

【Air 系列 · 鸿蒙 PC 适配全记录】22 / 56

■ 应用:AirMath(from GeoGebra)(AirMath)
■ 定位:GeoGebra 移植,函数绘图与几何计算
■ 上游:GeoGebra Classic (web) | 路线:ArkWeb 壳 | 版本:1.3.0
■ 开源: https://gitcode.com/Andy88/Harmony-GeoGebra
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917616688445919327
■ 官网(免费下载): https://www.shunidc.cn/

————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/

PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper

适配开源地址:https://gitcode.com/Andy88/Harmony-GeoGebra

环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743

▍一、为什么要适配 AirMath(GeoGebra Classic)

GeoGebra Classic 是数学教育领域使用最广泛的动态数学软件之一,把函数绘图、几何构造、CAS 符号计算、电子表格与概率统计放在同一个界面里。它的桌面版由数百万行 Java 与 giac 计算内核构成,多年积累的课件与教学流程都围绕它展开。对鸿蒙 PC 而言,这类工具决定的是数学场景能否真正落地:画出一条抛物线、求一个导数、保存一个 .ggb 课件,都是不可替代的基础能力。

工程文档 PORTING.md 的路线决策表把这个移植的难度说得很直接:desktop 模块(Java Swing)在鸿蒙上不可行,因为没有 JVM;纯 ArkTS 重写也不现实,因为内核规模太大。最终采纳的是第三条路——上游 GeoGebra 自带 GWT 编译链,org.geogebra.web.Tablet3D 模块可以直接产出 HTML5 应用,用 ArkWeb 壳承载它。这条路线此前已在 AirOffice 工程得到实证,AirMath 是它在数学软件上的一次复用。

当前鸿蒙工程的包名为 com.airmath.app,版本 1.0.0,deviceTypes 覆盖 phone、tablet 与 2in1,compatible SDK 与 target SDK 均为 6.1.0(23)。工程内没有任何 C/C++ 代码,全部逻辑由 ArkTS 壳、注入脚本与上游 Web 产物构成。需要说明的是,GeoGebra 采用非 OSI 开源许可证,移植以上游条款为准。

▍二、先划清边界:不把编译通过当成适配完成

对 AirMath 来说,"适配完成"的判断标准不是 Web 产物能否在 WebView 里显示,而是一条完整的用户链路能否闭合:启动出图、数学键盘输入、CAS 求值、导出 PNG、保存与重新打开 .ggb。任何一环断掉,这个应用就只是网页快照。因此工程在动手前先划定了下表的边界。

层次 上游 GeoGebra 实现 鸿蒙侧当前方案
应用入口 浏览器标签页加载 war/ 目录 Stage 模型 EntryAbility
界面承载 浏览器 DOM 与 GWT 部件 ArkWeb Web 组件加载 classic.html?lang=zh
资源供给 HTTP 服务器提供 war 静态目录 onInterceptRequest 从 rawfile/ggb/ 按需直读
文件保存 浏览器 File System Access API GgbBridge 桥接系统 DocumentViewPicker 保存
CAS 符号计算 giac Java/WASM 双实现 giac WASM 随 HAP 分发,本地运行
账号与在线素材 geogebra.org 登录与素材搜索 登录、搜索入口在壳内隐藏(离线移植无用)
设备间流转 无(浏览器无此能力) 碰一碰发送注册(knockShare)+ 系统分享接收

边界里最关键的一条是账号体系:Web 顶栏的登录按钮调用 GeoGebraJSNativeBridge.login(),在原生壳里只是一个没有响应的桩;顶栏放大镜是在线素材搜索,依赖 geogebra.org 接口。这两类入口被 boot.js 以 CSS、DOM scrub 与点击拦截三层手段隐藏,菜单行屏蔽同时用整词锚定避免误伤"搜索指令"这类本地功能。2026 年 9 月 19 日华为审核修复后,工程在 MateBook Pro 真机上完成了回归:汉堡菜单的文件行完整、无登录与搜索行,能力声明 dump 只剩 entity.system.home。

▍三、鸿蒙版的整体架构

整体架构可以概括为"一个本地域名、一条拦截链、一个单入口桥"。ArkTS 壳把 https://airmath.local/ 的所有请求劫持到 HAP 内的 rawfile 目录,web 侧在文档开始前注入 boot.js,把上游为平板应用预留的 GeoGebraJSNativeBridge 协议转发到 ArkTS 代理上。GWT 编译产物(war/tablet3d 与 war/css、后处理过的 classic.html)由 tools/package_web_assets.sh 打进 rawfile/ggb/。

text
EntryAbility(Stage 模型)
├── 沉浸式窗口:全屏布局 + 系统栏透明
├── 深浅色钉浅色(onConfigurationUpdate 带 DARK 守卫)
├── 共享 want 处理:立即读文件转 base64 入队
└── Index.ets
├── Web 组件:classic.html,javaScriptOnDocumentStart 注入 boot.js
├── onInterceptRequest → GgbAssets.serve()
│ https://airmath.local/<path> → rawfile/ggb/<path>
│ local/<path> → 沙箱 files/airmath/<path>
├── registerJavaScriptProxy('AirMath') → GgbBridge
│ command(json) 单入口:log/saveFile/pickOpen/appletReady/firstFrame
├── onShowFileSelector → 系统 DocumentViewPicker
└── 碰一碰发送:ggbApplet.getBase64 → cacheDir → SharedData


仓库主要目录如下:

text
AirMath/
├── AppScope/app.json5 # 包名 com.airmath.app、版本
├── PORTING.md # 路线决策、架构、踩坑与验收对照
├── tools/
│ ├── package_web_assets.sh # GWT 产物 → rawfile/ggb 打包流水
│ └── maven_relay.py # 依赖中继(TLS 受限网络下的构建支撑)
├── shots/ # 真机截图留档
└── entry/src/main/
├── module.json5 # 权限、skills、backup 声明
├── ets/
│ ├── entryability/EntryAbility.ets # 生命周期、窗口、共享 want
│ ├── ggb/GgbAssets.ets # 本地资源服务与 MIME 映射
│ ├── ggb/GgbBridge.ets # 单入口桥与文件读写
│ └── pages/Index.ets # Web 宿主、splash、碰一碰、避让区
└── resources/rawfile/
├── boot.js # 桥注入与浏览器 API 摘除
└── ggb/ # classic.html、css、GWT 编译产物


▍四、核心适配过程

### 1. 本地资源服务:airmath.local 与 onInterceptRequest

GgbAssets.ets 是整个方案的地基。Web 组件的每个请求都被 onInterceptRequest 交给 GgbAssets.serve(),按路径映射到 rawfile/ggb/,MIME 类型按扩展名逐项判定。这里有一个容易踩的差异:给二进制响应(wasm、png)追加 ; charset=utf-8 会破坏 ArkWeb 的 wasm 流式编译,因此编码声明只对文本类 MIME 生效。/local/ 前缀的请求指向沙箱工作目录,用于读回导出的文件;URL 映射里还做了父目录穿越防御。这套设计让 GeoGebra 以为自己仍在正常网站上运行,不感知任何离线壳的存在。

### 2. 单入口桥:command(json) 收敛代理表面

GgbBridge.ets 只向 Web 暴露一个 command(json) 方法,所有桥消息共用这一个入口,代理表面保持最小。保存走 DocumentViewPicker.save(),默认名以 .ggb 结尾;打开走 select() 后读文件转 base64。桥回调页面一律通过 setTimeout 延迟 50ms 执行——在代理调用栈上直接重入 WebView 会把 ArkWeb 挂死,这是 AirOffice 项目留下的教训,注释中明确记录。共享文件到达时若 applet 尚未就绪,enqueueSharedFile 会先入队,等页面发出 appletReady 信号后再冲队投递,冷启动与热启动两条路径由此统一。

### 3. boot.js:接管上游预留的原生壳协议

上游 GWT 代码本身就为平板应用预留了 window.GeoGebraJSNativeBridge 协议(share、saveFile、openFromFileClickedNative 等)。boot.js 在文档开始前把它安装好,转发到 AirMath 代理;文件打开回调 __amOnFileOpened 直接喂给 ggbApplet.setBase64。最有代表性的一处是本地保存的强制改道:ArkWeb 实现了 File System Access API,上游 SaveControllerW 优先使用 window.showSaveFilePicker,而它的选择器没有文件名输入框。由于 isSupported() 是穿透原型链的 has() 检查,boot.js 沿 Object.getPrototypeOf 上溯找到属性定义处将其删除,上游随即回退到 FileManagerT.export(),保存流程落回桥上。

### 4. 窗口、输入与显示形态

EntryAbility.ets 把应用钉在浅色:GeoGebra Classic Web 没有暗黑主题,跟随系统深色会出现暗底深字。onConfigurationUpdate 里必须先判断新模式确实是 DARK 再调 setColorMode,否则设置动作自身再次触发回调,递归栈溢出直接 jscrash。Index.ets 处理沉浸式避让:取系统栏、导航指示器与挖孔区的原始像素,通过 window.__amSetInsets 推给页面由 boot.js 收缩 body 盒——直接给 Web 组件加 padding 无效,ArkWeb 仍按全窗口渲染而命中区缩小,渲染与命中错位。输入侧,应用自带数学键盘,onInterceptKeyboardAttach 与 setSoftKeyboardBehaviorMode 双保险确保系统输入法永不弹出,且跨前后台切换保持。

### 5. 汉化、启动体验与体积

汉化走上游自带的中文链路:URL 固定 ?lang=zh,配合 properties_keys_zh-CN.js,菜单与占位符全中文。原生 splash 覆盖 GWT 启动期,页面首帧信号、15 秒超时或用户点按三者任一触发即撤除。启动性能有两级数据:MatePad Mini 冷启动 aa start 到 appletReady 约 2.2 秒(达标线 3 秒),MateBook Pro 回归时 appletReady/FMP 1526 毫秒。HAP 从 52.7MB 瘦身到 28.3MB,手段是剔除 80 个未使用语言文件,只保留 zh-CN、zh-TW 与 en。

▍五、适配中遇到的几个难点

### 难点一:flex UTD 哈希漂移导致商店审核拒审

为了让文件管理器"打开方式"识别 .ggb,工程曾注册 viewData skill 并在 rawfile/arkdata 打包自定义 UTD。但这个 OS 版本对未知扩展名生成确定性合成类型 flex.<hash>,连应用自己查询 .ggb 都返回合成 id;更麻烦的是该哈希随 OS 版本漂移,AGC 审核设备上三个声明 type 全不匹配,点 .ggb 报"暂无可用打开方式",构成 linkFeature 拒审。解法是整条撤销:viewData skill 与自定义 UTD 移除,.ggb 载入改由应用内"打开"(系统选择器)承担,碰一碰发送不依赖 skill 予以保留。这条记录也说明文件关联类能力必须按目标机型的真实解析行为验证,不能只看本地设备。

### 难点二:钉浅色的递归栈溢出

现象是 MatePad Mini 上 jscrash,栈顶在 onConfigurationUpdate。原因是 setColorMode 本身会再次触发 onConfigurationUpdate,无守卫的重设让回调自我触发形成无限递归。解法是加 DARK 判断守卫:只有进入配置确实是深色时才重设浅色,两个方向不再互相激励。这个坑的价值在于它揭示了颜色模式 API 的反馈回路,此后工程内所有 colorMode 相关代码都带着这个守卫。

### 难点三:构建期 TLS 链路被掐

上游构建依赖 Gradle 9.4.1 wrapper、repo.geogebra.net 与 maven central,而 Java 的 HttpsURLConnection 在工程网络环境下握手被断(curl 却正常),wrapper 直连超时。对策分三层:wrapper 用腾讯镜像预置 zip 到 ~/.gradle/wrapper/dists/;依赖走 tools/maven_relay.py 流式中继加阿里云镜像;dart-sass 预置到本地。中继还会引入新问题——慢链路截断的 jar 的 sha1 可能恰好对上,因此构建前用 python zipfile 对 geogebra 系构件做全量完整性检查。上游构建链的脆弱程度不亚于移植本身,这部分工作全部沉淀在 tools/ 与 ai_log/ 里。

### 难点四:giac-gwt sources jar 上游损坏

CAS 符号计算依赖的 giac-gwt sources jar 在上游就是坏的,直接编译无法进行。工程手写重建了 CASResources 与 Impl 的源码,giac.wasm.js 运行时从 giacjs/ 取用。这条路径的真机实证记录在 PORTING.md:7×6 求值返回 42,x² 求导返回"导数:2x",截图留档于 shots/pc-cas-eval.png 与 shots/pc-cas-deriv.png。CAS 是 GeoGebra 的核心价值之一,这块断不了,移植才算站住。

### 难点五:真机签名与渠道差异

release 证书直接安装报 9568322 not trusted app source,真机部署需要单独的签名产物。工程为此在 build-profile 里新增 dev product 挂 debug 签名,与 release(上架用)并列,真机回归统一走 dev 渠道。这个决定发生在华为审核修复期间,保证了"审核版本"与"开发版本"的构建路径互不干扰。

▍六、编译、安装与启动

AirMath 的 HAP 构建不涉及 native 交叉编译,标准 hvigor 流程即可。Web 资产已由 tools/package_web_assets.sh 流水生成并入库;若修改上游重新出包,需先重跑该脚本再打包。工程按惯例不入库 build-profile.json5(模板见 build-profile.example.json5),本地需配置 default 与 release 两套签名材料,真机部署用 dev product。

macOS 命令行构建示例如下:

bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk

hvigorw --mode module -p module=entry assembleHap --no-daemon


签名产物位于 entry/build/default/outputs/default/entry-default-signed.hap。连接设备后安装并启动:

bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airmath.app


PORTING.md 记载的真机验证覆盖 MatePad Mini 悦读版(2560x1600)与 MateBook Pro(3120x2080)两台设备,包括启动渲染、数学键盘出图、CAS 求值、导出保存闭环与 .ggb 往返,截图证据保留在 shots/ 目录。

▍七、当前功能边界

当前版本已覆盖的能力,全部有真机验证记录支撑:

- 可安装 HAP、Stage 模型入口与 ArkWeb 全屏承载;
- 启动渲染完整 UI(工具栏、代数区、坐标系、数学键盘),冷启动约 2.2 秒;
- 菜单与占位符全中文(?lang=zh 链路);
- 数学键盘输入与绘图出图;
- CAS 符号计算(giac WASM):求值与求导真机实证;
- 导出 PNG 与 .ggb 保存(桥接系统保存选择器,273KB PNG 与 9797B .ggb 落盘记录在案);
- .ggb 打开载回(应用内"打开"→系统选择器);
- 剪贴板写图像(ArkWeb 原生 navigator.clipboard,hidumper 3701 显示 owner=com.airmath.app);
- 碰一碰发送注册(2in1 带 windowId);
- HAP 体积 28.3MB,登录与在线搜索入口已按离线定位隐藏。

尚未覆盖或验证的能力,依据 PORTING.md 的遗留清单:PC 鼠标交互(S3 的 PC 半边)、手写笔(S4)、双指捏合缩放(S5)与光标验证(S6)未系统测试;碰一碰发送的双机物理相碰与 2in1 沙箱接收(harmonyShare dataReceive)未接;listLocalFiles/getBase64 经确认是上游死接口(全仓零调用方),入档关闭不接。因此,当前版本更准确的定位是"GeoGebra Classic 的鸿蒙 PC 离线数学工作站基础版":绘图、计算与课件文件闭环已经真实可用,设备间流转与输入形态验证是下一阶段的事。

▍八、总结

AirMath 的适配价值在于验证了一条对大型 Web 化桌面软件普遍适用的路径:不重写内核,用 ArkWeb 承接上游自带的 Web 产物,把平台差异收敛到一个本地资源服务、一个单入口桥和一段注入脚本里。文件闭环没有走通之前,"能在 WebView 里显示"毫无意义;走通之后,CAS 求值、导出保存、课件往返这些真实教学动作才在鸿蒙 PC 上成立。工程同时留下了完整的踩坑档案——UTD 漂移、递归栈溢出、TLS 中继、损坏的 sources jar——对后来者都是可复查的实证。

后续工作的重心按 PORTING.md 的遗留清单展开:先补齐 PC 鼠标、手写笔与捏合缩放的真机系统验证,再打通碰一碰的双机物理链路与 2in1 接收半边。每一步的验收口径不变:以可构建的 HAP 与真机上的实际行为为准,截图与 hilog 留档。
楼主 8 天前
想参与讨论?登录 或 30 秒注册。