Air 系列应用交流/AirOffice(ONLYOFFICE)· 鸿蒙 PC 适配全记录

AirOffice(ONLYOFFICE)· 鸿蒙 PC 适配全记录

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

■ 应用:AirOffice(AirOffice)
■ 定位:ONLYOFFICE 移植,文档/表格/幻灯办公套件
■ 上游:ONLYOFFICE Desktop Editors | 路线:ArkWeb 壳 | 版本:1.4.0
■ 开源: https://gitcode.com/Andy88/Harmony-ONLYOFFICE
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917614384941926072
■ 官网(免费下载): https://www.shunidc.cn/

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

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

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

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

▍一、为什么要适配 AirOffice

办公三件套是桌面操作系统不可回避的验收项。文字、表格、演示文档的查看与编辑,决定了用户能否把日常工作迁到一台新设备上。在 Linux 桌面生态里,ONLYOFFICE Desktop Editors 是少数对 OOXML 格式保持高保真的开源办公套件,它的一个关键架构事实是:编辑器 UI 与文档渲染本身就是 Web 技术(web-apps 提供界面,sdkjs 在 canvas 上渲染文档),桌面版的 C++/CEF 壳只是宿主,负责本地服务、文件对话框和格式转换调度。

这个事实直接决定了移植思路。既然编辑器本体是 Web 应用,鸿蒙侧就用系统自带的 ArkWeb(同为 Chromium 内核)替代 CEF 宿主;而真正无法绕开的原生部分——x2t 文档转换内核(OOXML 与内部 bin 格式互转)——则用 OHOS NDK 交叉编译成一组 .so,经 NAPI 桥接入应用。这正是"复用现有引擎"路线的典型落地:不移植整个 Chromium,也不重写编辑器,只替换宿主与接线。

当前鸿蒙工程包名为 com.airoffice.app,版本 1.3.0,deviceTypes 覆盖 phone、tablet 与 2in1,入口为 Stage 模型 EntryAbility,orientation 声明为 auto_rotation。上游对应 ONLYOFFICE Desktop Editors v9.4.0,依照 AGPL v3 发布,内置 HarmonyOS Sans SC、Carlito、Caladea 等字体随包分发。项目文档记载了在 HUAWEI MatePad 11.5(HarmonyOS 7.0)与 HUAWEI MateBook Pro(2in1 PC)两台真机上的功能验证记录。

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

ONLYOFFICE 桌面版在 Linux 上是一整套 CEF 应用:壳进程启动本地 HTTP 服务托管编辑器页面,通过 window.AscDesktopEditor 对象向前端暴露打开、保存、字体、剪贴板等原生能力,x2t 以独立转换器进程的形式被调度。如果只把 web 资源塞进任意浏览器打开,得到的将是一个没有文件进出、没有打印、没有保存回流的演示页。

因此本工程把边界划在"壳层全量自研替换、引擎原样复用、内核交叉编译"三条线上:ArkTS 壳承担 CEF 壳的全部职责(资源服务、桥接 API、文件流转、打印、沉浸与输入处理),sdkjs/web-apps 编辑器本体不做改动地运行在 ArkWeb 中,x2t 则以库形态在 native 层常驻。每一层的能力归属如下表。

层次 原项目实现(Linux 桌面版) 鸿蒙侧当前方案
应用入口 C++/CEF 壳进程与消息循环 Stage 模型 EntryAbility + Index.ets
界面承载 CEF 浏览器窗口 + 本地 HTTP 服务 ArkWeb Web 组件 + onInterceptRequest 静态资源拦截
壳桥接 API CEF 实现的 window.AscDesktopEditor javaScriptProxy 注册的 DesktopBridge.ets
编辑器引擎 web-apps + sdkjs(Web 技术) 原样复用,运行于 ArkWeb
格式转换 x2t 桌面转换器进程 NDK 交叉编译 libx2t.so + NAPI x2t_bridge.cpp
打印 系统打印对话框 活编辑器自渲染 PDF + print.print 系统打印面板
文件进出 本地文件系统直读直写 沙箱中转 + Download 直写 + 系统 Picker 另存

边界之外的部分同样明确:CEF 不移植(体量约等于移植整个 Chromium),v8 不交叉编译(活编辑器自渲染已取代 DoctRenderer 完整版的需求),拼写词典与帮助文档按体积取舍。这个边界使每一步交付都可以用"文件进出是否无损、打印是否出页"这类用户可感知的标准来验收,而不是用编译日志自证。

▍三、鸿蒙版的整体架构

整条链路可以概括为:ArkTS 壳拉起 ArkWeb 加载编辑器页面,请求被 onInterceptRequest 拦截后由 rawfile 与沙箱供给静态资源;页面通过 javaScriptProxy 调用 DesktopBridge 获得壳能力;文档打开与保存经 X2TService 下沉到 native x2t 完成 OOXML 与 bin 格式互转;保存回流由编辑器 asc_Save 协议触发,壳取回 bin 后转换写盘。

text
EntryAbility(权限申请、Want/URI 分发、文件后缀解析)
└── Index.ets(壳主页面:打开/保存/另存/引擎切换/保存回流)
├── ArkWeb Web 组件
│ ├── onInterceptRequest → rawfile/沙箱静态资源(替代 CEF 本地 server)
│ ├── javaScriptProxy → DesktopBridge(window.AscDesktopEditor API 面)
│ └── boot.js 注入(保存接管、IME 门控、insets、打印服务注入)
├── X2TService.ets(格式常量表、串行队列)
│ └── NAPI → x2t_bridge.cpp → libx2t.so(dlopen 按需加载约 30 个模块库)
└── 保存回流:StartSave 协议 → 壳拉取 bin(base64)
→ x2t bin→docx/xlsx/pptx → Download 直写 / Picker 另存


主要目录如下:

text
AirOffice/
├── AppScope/app.json5 # 包名 com.airoffice.app、版本 1.3.0
├── BUILD.md # 构建部署与真机调试速查
├── PORTING.md # 移植踩坑实录(坑与契约速查索引)
├── entry/src/main/
│ ├── module.json5 # 权限、分享/打开方式 UTD 声明、auto_rotation
│ ├── ets/
│ │ ├── pages/Index.ets # 壳主页面与保存回流全链路
│ │ ├── editor/DesktopBridge.ets # AscDesktopEditor API 的 ArkTS 实现
│ │ ├── editor/X2TService.ets # x2t 转换服务与格式 ID 常量
│ │ ├── editor/EditorAssets.ets # 编辑器资源解包与供给
│ │ └── editor/SystemFonts.ets # 系统字体探测
│ ├── cpp/
│ │ ├── x2t_bridge.cpp # NAPI 桥:convert/convert_mode
│ │ └── procdir_override.cpp # 进程目录覆盖(OHOS 只读 /system/bin)
│ └── resources/rawfile/
│ ├── boot.js # 页面侧注入脚本(保存/IME/insets/打印)
│ ├── editors/ # web-apps + sdkjs 编辑器本体
│ ├── converter/ # x2t .so 集合 + icudt 数据
│ └── fonts/ # 内置字体集
└── native/
├── desktopeditors-src/ # 上游 core/sdkjs/web-apps 快照 + OHOS 改动
├── mkspecs/linux-ohos-clang/ # qmake 交叉编译 spec
└── scripts/ # build_core_all.sh / deploy_converter.sh / gen_allfonts.py


▍四、核心适配过程

### 1. 用 onInterceptRequest 复刻 CEF 本地服务

桌面版的编辑器页面由 CEF 壳的本地 HTTP 服务托管。鸿蒙壳改用 onInterceptRequest 拦截页面请求,从 rawfile 与首启解包的沙箱目录供给静态资源。这条路上有两个被 PORTING.md 记录为契约的细节:Web 组件必须开启 onlineImageAccess(true),否则工具栏图标全部空白;wasm 等二进制响应不能附加 charset,否则 sdk-all 的 wasm 加载失败。字体侧则反其道行之——native 的 LoadFontBase64 通道必须不存在,字体加载由 boot.js 包装处理,避免与页面侧字体管线冲突。

### 2. DesktopBridge 补齐 window.AscDesktopEditor API 面

CEF 壳与前端之间靠 window.AscDesktopEditor 对象交互,API 面覆盖打开、保存、字体、剪贴板、打印等。entry/src/main/ets/editor/DesktopBridge.ets 通过 javaScriptProxy 逐项实现该 API 面:LocalFileSave 承接保存与导出分流(第 4 参携带 c_oAscFileType,PDF=513 与 x2t 格式 ID 同值);Copy/Cut/Paste 复刻 CefFrame 语义——粘贴读取系统剪贴板后向 iframe 注入合成 ClipboardEvent,复制则回读引擎填充的 DataTransfer 写回系统剪贴板;Print 对接系统打印。桥还实现 getEngineVersion(): 108,让 sdkjs 走旧版 _private_oncopy 路径以适配合成事件。

### 3. 原生 x2t 内核的交叉编译与 NAPI 接线

native 层用宿主机 qmake 加自定义 spec mkspecs/linux-ohos-clang(OHOS NDK clang、--sysroot、-D__MUSL__、libc++、lld)逐个编译 core 下约 30 个模块,产物为 libx2t.so 及其运行时 dlopen 的依赖库集合。musl 差异以 DEFINES+=NOT_USE_PTHREAD_CANCEL 处理,harfbuzz 打符号字体 charmap 回退补丁,中文字体别名(宋体、微软雅黑映射到 HarmonyOS Sans SC)由 scripts/gen_allfonts.py 生成 AllFonts.js。entry/src/main/cpp/x2t_bridge.cpp 提供 convert 与 convert_mode 两个 NAPI 入口,X2TService.ets 维护格式常量表并以 runExclusive 串行队列约束并发转换。

### 4. 保存回流协议与壳侧接管

保存链路最终定型为:壳注入 DesktopOfflineAppDocumentStartSave(引擎协议的 Start 半段,无门禁)拉起配对流程,编辑器回调 LocalFileSave 交付 bin,壳经 x2t 转 OOXML 后写 Download 目录或交系统 Picker 另存。boot.js 在页面侧包装 asc_Save 与内部 n1,把一切用户保存请求转发壳侧,绕开混淆代码里 M8/YIe/aSb 四层门禁链;保存成功后注入 SetUnchangedDocument() 清引擎脏标志,避免返回时误弹保存询问。v2 管线还实现了 download 模式静默建家、saveUri 原地写回与 45 秒节流自动保存。

### 5. 分享、打开方式与系统打印接入

module.json5 声明了 ohos.want.action.sendData 与 viewData 两组 skills,按后缀解析的 UTD 覆盖 docx/xlsx/pptx/doc/xls/ppt/pdf 七类办公文档,使 AirOffice 同时成为系统分享面板的接收目标与文件管理器"打开方式"的候选。打印侧,桥实现 Print 后复用活编辑器渲染管线:注入 JS 临时置真 NATIVE_EDITOR_ENJINE,走 asc_nativeCalculateFile → asc_nativeGetPDF 产出 pdf.bin 渲染流,再由 x2t bin2pdf 纯 native 转 PDF(三段中英混排实测 167KB/2 页/2 字体),print.print([uri], ctx) 拉起鸿蒙系统打印预览;boot.js 同时注入"华为打印服务"虚拟打印机,解决无打印机时 DE 打印主按钮禁用的问题。

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

### 难点一:保存入口在混淆代码里,错了就静默空转

现象是点保存无任何反馈或卡"正在下载文件"转圈。逆向 9.4.0 部署包后定位到 asc_Save 在本地离线模式下第一道门恒假直接 return,未修改文档时脏标志 M8 为 false 也会静默跳过;downloadas URL 只有 CEF 本地服务才有,ArkWeb 拦截器返回 404 后页面永挂。解法是放弃从引擎正面进入,改注入无门禁的 DesktopOfflineAppDocumentStartSave 并配对 EndSave,辅以 450ms 防抖与 savePromptShowing 重入守卫。此坑的教训被写入速查索引:保存出空 docx 时还需检查 bin 是否被补了双头(bin 自带 DOCY;v10;0; 头)。

### 难点二:sdkjs 双 bundle 导致二次打开崩溃

同会话第二次投递文档时 TypeError: this.UEd.ek is not a function,旧画布残留。逆向确认 sdk-all-min.js(1.27MB)是 wasm 胶水前置半部,sdk-all.js(9.9MB)是依赖前者全局的主体半部,二者是同一分割构建的两半而非冗余副本;二次 EndLoad 在 wasm 桥内部状态上重入崩溃。尝试把 require.config 直指 sdk-all.js 的实验首开即挂。最终修复采用与上游 CEF 壳一致的重开语义:同引擎重开一律整页 reload,转换产物等开机 onLocalOpen 握手再投递,代价是多付一次约 8 到 10 秒的 sdk 解析。

### 难点三:PDF 导出不需要 v8,需要活编辑器

bin→PDF 的标准路径依赖 x2t 内嵌 DoctRenderer,而移植版里它只是 6KB 的 stub,DoctRenderer.Execute 返回 rc=80。排查中先后尝试无头 iframe 复刻渲染环境(死在 v8 注入的 CNativeControlEmbed 对象)与 v8 交叉编译(源码获取与构建镜像双重受阻),两条路都关闭后复盘发现"空 PDF"的真正原因是测试输入走了 IME 组合层从未进文档。最终方案完全绕开 DoctRenderer:活编辑器自身渲染能力 asc_nativeGetPDF 直接产出渲染流,x2t 只做纯 native 的流转封装。

### 难点四:触屏设备的软键盘与手势语义

桌面模式 sdkjs 让隐藏捕获元素 #area_id 常驻聚焦以保证快捷键可达,CEF 下无感,ArkWeb 却会因焦点异步拉起软键盘,导致手机上点任何按钮都弹键盘。boot.js 按"最近一次指针手势"门控 focus:画布内放行、chrome 区域吞掉并补刀 blur,且只监听 pointerdown 以免兼容性合成 mousedown 毒化分类。拖拽族问题的根因则是 sdkjs 桌面管线只对 pointerType==='mouse' 的指针事件完整工作,而 Chromium 只为轻点合成鼠标序列——工程以 touch→mouse 指针翻译 shim 补上这层移植断层,并修正了 CheckNeedWheel() 反相导致滚轮管线自废的问题。

▍六、编译、安装与启动

native 内核如需重编,按 native/README.md 的顺序执行:scripts/build_core_all.sh 产出 desktopeditors-src/core/build/lib/linux_arm64/ 下的模块库,scripts/deploy_converter.sh 部署转换器 .so 集合、icudt74l.dat 与空文档模板到 HAP 目录,python3 scripts/gen_allfonts.py <rawfile/fonts> <sdkjs/common/AllFonts.js> 生成字体别名表。仓库已提交预编译产物时可直接进行 ArkTS 构建。

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。连接设备后安装并启动(需先为 com.airoffice.app 配置调试签名,READ_PASTEBOARD 属受限权限,Profile 需带 ACL):

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


BUILD.md 还提供了 devecocli run --device <序列号> 的一条命令部署路径,以及 CDP 调试(hdc fport 转发 webview_devtools_remote 端口)与 hilog 流式日志的速查命令。项目文档记录的真机验证覆盖三引擎新建、打开、中文渲染输入、保存往返校验与另存为。

▍七、当前功能边界

当前版本已覆盖的能力:

- word/xlsx/pptx 三引擎的新建、打开(系统选择器与"打开方式")、中文渲染与中文输入;
- 保存直写 Download 目录(内容往返无损、sharedStrings 与 slide XML 校验通过)与另存为;
- 三引擎互相切换,分享面板与碰一碰接收(sendData UTD 声明);
- PDF 导出(活编辑器自渲染 + x2t bin2pdf)与华为系统打印面板接入;
- 右键复制/剪切/粘贴(合成 ClipboardEvent 桥 + READ_PASTEBOARD 权限链);
- 触屏 IME 门控、touch→mouse 指针翻译、底部沉浸与横竖屏避让区适配;
- 深色模式跟随(页面侧 prefers-color-scheme 上报 + uitheme 参数);
- 全新安装冷启动投递(120 秒重试窗口 + x2t 串行队列)。

尚未覆盖或仍在边界之外的能力:CEF 壳的插件与宏体系未迁移;x2t 内嵌 DoctRenderer 仍为 stub(PDF 已由活编辑器方案绕开,但离线批量转换场景仍依赖活编辑器在场);拼写检查词典按体积取舍未全量携带;sdkjs 内部符号随版本变化,协议适配以 9.4.0 build 129 为基准,升级上游需要重新核对混淆名。因此当前版本更准确的定位是"ONLYOFFICE 办公三件套的鸿蒙原生宿主版":编辑器引擎与转换内核来自上游原样能力,壳层的资源服务、保存协议、打印、输入与安全区适配全部为鸿蒙侧自研实现。

▍八、总结

AirOffice 的适配过程说明了一类应用的移植路径:当上游的核心资产本身就是 Web 技术时,把力气花在宿主替换上比移植整个浏览器框架更划算。工程用 ArkWeb 接管了 CEF 的页面托管与桥接职责,用 NAPI 加交叉编译把 x2t 转换内核留在 native 层,再用 boot.js 注入层弥合桌面语义与移动/PC 混合形态之间的差距。最终产物是一个可以在鸿蒙手机、平板与 2in1 PC 上安装启动、读写真实 OOXML 文档、导出 PDF 并接入系统打印的办公套件。

从 PORTING.md 的风险登记册与后续记录看,工作重心将集中在三处:上游 sdkjs 版本升级时的混淆符号与协议核对、词典与帮助资源的体积优化、以及长时编辑会话下的稳定性回归。每一项都以文档往返无损与真机功能清单作为验收基准,与项目一贯的实证方法保持一致。
楼主 8 天前
想参与讨论?登录 或 30 秒注册。