HarmonyOS 应用要在端内预览或编辑 Word、Excel、PPT,常见做法是集成 @wps/wps_sdk,用 WPSApi 拉起本机已安装的 WPS。打开动作本身不分散在多个入口类里:构造 OpenFileRequest,填路径与是否可编辑,再调用 WPSApi.sendRequest。联调里把「点按钮没反应」写成打开失败,多数时候其实是注册未完成、路径不在应用沙箱,或把 Promise 异常当成了 Result.code。本文按接口语义写打开链路:构造参数、沙箱拷贝、enableEdit、结果分支与可复用封装。字段以官方对接文档为准,随 HAR 批次核对后再合入。

一、打开链路在 SDK 调用中的位置

完整顺序固定为:集成 HAR → 启动阶段 registerApp → 回调到 ResultCode.OK → 按凭据约定可选调用 setWpsFileToken → 把待打开文件拷进本应用沙箱 → new OpenFileRequest(context, path) → 设置 enableEditsendRequest。打开层只消费已经就绪的 SDK 与可读路径,不负责签发 appKey

层级职责入口
依赖HAR 与包名绑定凭据@wps/wps_sdk
门禁应用注册registerApp
打开沙箱路径、只读或可编辑OpenFileRequest
调度拉起 WPS 并返回结果WPSApi.sendRequest

当前请求类型只有 OPEN_FILE。水印、extraOptions、关窗回传都是 OpenFileRequest 上的附加字段,不要在只读打开尚未稳定时一起打开。出现 1013 时暂停改路径与 enableEdit,先对齐 bundleName、HAR 与申请归档。

二、构造参数:context 与 fileUri

签名是 new OpenFileRequest(context: common.UIAbilityContext, fileUri: string)。两个参数都必填。context 必须是当前 UIAbility 的上下文,页面里用错 Ability 或把 undefined 传进去,打开侧常见泛化 ResultCode.ERRORfileUri 文档写成本地文件路径;实践上应传入本应用沙箱内可读路径。系统文件选择器给出的 URI 往往没有跨进程读权限,WPS 进程打不开,日志却不一定写「权限」二字。

建议在打开前把源文件拷到 context.filesDir 下的固定子目录,再把拷贝后的路径交给构造函数。目录按业务拆分可以,但全仓应共用同一个拷贝函数,避免每个页面各写一套临时文件名规则。

import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';

export function copyIntoSandbox(
  ctx: common.UIAbilityContext,
  src: string,
  ext: string = 'docx'
): string {
  const dir = `${ctx.filesDir}/wps_inbox`;
  fs.mkdirSync(dir, true);
  const dest = `${dir}/${Date.now()}.${ext}`;
  fs.copyFileSync(src, dest);
  return dest;
}

拷贝失败应在打开前抛给调用方,不要带着半截路径去 sendRequest。扩展名与真实文件类型保持一致,避免客户端按后缀解析失败。调试包与商店包的 filesDir 相互隔离,不要把调试机上的绝对路径写进正式包配置。

三、enableEdit:未赋值即只读

赋值实际模式
未设置只读(ReadOnly)
false只读
显式 true可编辑(Normal)

预览入口与编辑入口应走同一封装,只差一个布尔参数。两套 new OpenFileRequest 很容易在后续叠水印时漂移。enableEdit 与关窗回传独立:能编辑不等于必须回传;只读预览也可以不设 wpsTransferType。联调顺序建议:注册 OK → 沙箱只读打开 → 同一路径 enableEdit = true → 再叠策略字段。

不推荐在每次构造时写 request.wpsToken。需要激活序列号时,在 registerApp 回调 ResultCode.OK 之后调用 WPSApi.setWpsFileToken,后续所有打开请求自动携带。Request 字段与全局设置同时存在时,以全局设置为准,日志会误导排查。

四、sendRequest 的两种失败形态

WPSApi.sendRequest(request) 返回 Promise<Result>。对接文档写明:注册尚未成功时抛异常,而不是返回一个带 codeResult。UI 必须同时处理 .then 里的非 OK,以及 .catch 里的异常。把异常文案直接展示成「文档损坏」会误导测试。

Result 常用字段:requestTypecodemsgdata。未开启关闭回传时,拉起成功即 code === ResultCode.OKdata 为空,这是正常语义,不要弹「已保存」。开启回传后,用户关窗且回传成功才在 data 里带 fileUritransferFd

现象优先核对
Promise reject是否等到注册 OK
1013appKey / appSecret / bundleName
ResultCode.ERROR(-2)路径是否沙箱、Context 是否有效
OK 且 data 为空是否根本没开回传
import { WPSApi, OpenFileRequest, Result, ResultCode } from '@wps/wps_sdk';
import { common } from '@kit.AbilityKit';

export async function openLocalDoc(
  ctx: common.UIAbilityContext,
  sandboxPath: string,
  editable: boolean
): Promise<Result> {
  const req = new OpenFileRequest(ctx, sandboxPath);
  req.enableEdit = editable;
  try {
    const result = await WPSApi.sendRequest(req);
    console.info('[WPS] open', result.code, result.msg ?? '', !!result.data);
    return result;
  } catch (e) {
    console.error('[WPS] sendRequest threw', e);
    throw e;
  }
}

日志前缀固定为 [WPS] open[WPS] register,便于把打开失败和注册失败拆开看。Release 构建不要打印完整 appSecret 或序列号。

五、把注册门禁和打开函数拆开

页面点击回调里直接 registerApp 再立刻 sendRequest,时序上几乎必然撞上未注册异常。把注册做成可 await 的一次性准备,打开函数只检查就绪标记。

let sdkReady = false;

export function waitUntilRegistered(
  appKey: string,
  appSecret: string,
  activationSn?: string
): Promise<void> {
  return new Promise((resolve, reject) => {
    if (sdkReady) {
      resolve();
      return;
    }
    WPSApi.registerApp(appKey, appSecret, {
      onCallback: (r: Result): void => {
        if (r.code === ResultCode.ERROR_CODE_AUTH_FAILURE) {
          reject(new Error(`1013:${r.msg ?? ''}`));
          return;
        }
        if (r.code !== ResultCode.OK) {
          reject(new Error(`register:${r.code}`));
          return;
        }
        if (activationSn) {
          WPSApi.setWpsFileToken(activationSn);
        }
        sdkReady = true;
        resolve();
      },
    });
  });
}

冷启动在 Ability onCreate 里发起 waitUntilRegistered。打开按钮用 sdkReady 控制 enabledactivationSn 由构建配置注入:需要序列号的产物传真实值,不需要的产物传空,封装内部跳过 setWpsFileToken。不要在每个 Page 的 aboutToAppear 里再调一次注册。

六、联调清单与现象对照

建议写进测试用例而不是口头约定:

  1. 冷启动日志出现注册 code=0
  2. 选择器文件经 copyIntoSandbox 后再打开
  3. 只读入口未写 enableEdit = true,工具栏不可改
  4. 编辑入口显式 true,可保存
  5. 未开回传时 OK 且 data 为空,UI 不提示保存成功
  6. 未就绪时点击打开,走 catch,不伪造 Result
  7. 换 HAR 后 ohpm install 并 clean,重跑只读打开
口头描述更可能的原因
点了没反应按钮未等 sdkReady,异常被吞
提示失败但文件能在文件管理器打开没拷沙箱
以为只读也能改漏写 enableEdit 或写成了 true
以为保存成功把拉起 OK 当成回传 OK

策略字段放到只读与可编辑都稳定之后再叠。extraOptions 仅显式赋值的项生效,不要整表拷默认值。换 flavor 后 bundleName 与凭据必须重新归档;调试包绿、商店包红,优先查包名而不是再改 OpenFileRequest 字段。

七、小结

OpenFileRequest 打开本地文档可以收成四条工程纪律:注册等到 ResultCode.OK;文件先进入本应用沙箱;默认只读,可编辑必须显式赋值;sendRequest 的异常与 Result.code 分开处理。统一版把打开面收敛到一套 API,工程上仍要把路径与时序做对。把 waitUntilRegisteredcopyIntoSandboxopenLocalDoc 分成三个符号后,预览页与编辑页只差一个布尔值,后续叠水印或回传也不必再复制一套构造代码。

接入评审时把当前 HAR 文件名、凭据环境、是否注入序列号、沙箱目录约定写成一页对照。测试按表验收,比口头说「都能打开」可靠。合入后全仓搜索 new OpenFileRequest,命中应集中在打开封装文件。外部 URI 权限不足时常见泛化 ERROR;正式包与调试包包名不同时,凭据批次分开存放。字段语义继续以官方对接文档为准,随 SDK 小版本更新注释,而不是把整张参数表贴进业务页。


基于 WPS Open SDK 鸿蒙版对接实践整理,仅供开发者参考。
官方对接文档:https://365.kdocs.cn/l/clQl5cek2NoT

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐