把 WPS Open SDK 接进 HarmonyOS 工程后,registerApp 经常被当成"启动时顺手调用一下"的代码。真正上线时,这段初始化会同时面对页面重建、按钮连点、调试包与正式包切换、HAR 升级等变量。只要某个入口在注册回调到达前发起 sendRequest,日志里就会出现看似是文档打开失败、实际却属于初始化时序的异常。
更稳的做法不是给调用点补几个 if,而是把注册视为应用级就绪门禁:由一个模块负责注册、重试和状态广播,页面只等待就绪结果。本文从架构分工出发,给出一套适合鸿蒙页面生命周期的最小实现,并把 1013、空凭据和未注册异常拆到不同排查路径。
一、注册应该属于应用层,而不是按钮层
WPSApi.registerApp(appKey, appSecret, callback) 完成的是应用调用资格校验。校验成功前,sendRequest 不具备执行条件;失败后也不应该继续叠加文档参数实验。因此它天然属于应用初始化,而不属于某个页面的点击动作。
一个常见错误是把注册写在文档页的 aboutToAppear。页面返回一次就注册一次,两个 Tab 同时存在时还可能并发注册。即使回调都返回成功,业务侧保存的就绪状态也可能互相覆盖。更麻烦的是,用户已经进入页面并点击打开时,首次注册还没有结束,最终只能收到 Promise 异常。
可以把数据流固定为:
| 层级 | 责任 | 对外结果 |
|---|---|---|
| 应用初始化层 | 调用 registerApp、处理回调、设置序列号 |
ready / failed |
| 文档服务层 | 等待就绪、构造请求、调用 sendRequest |
Result 或异常 |
| 页面层 | 展示初始化、打开成功或失败状态 | 用户提示 |
这样拆分后,页面不需要知道凭据,也不需要判断当前 SDK 形态。它只消费一个明确状态。
二、用 Promise 消除重复注册
注册回调是异步的,页面可能在同一时间多次请求打开。用一个 Promise 表示"初始化进行中",能把并发调用合并到同一次注册上:
typescript
import { WPSApi, Result, ResultCode } from '@wps/wps_sdk';
type WpsState = 'idle' | 'booting' | 'ready';
let state: WpsState = 'idle';
let bootTask: Promise<void> | null = null;
export function bootWps(
appKey: string,
appSecret: string,
activationSn?: string
): Promise<void> {
if (state === 'ready') {
return Promise.resolve();
}
if (state === 'booting' && bootTask) {
return bootTask;
}
state = 'booting';
bootTask = new Promise<void>((resolve, reject) => {
WPSApi.registerApp(appKey, appSecret, {
onCallback: (result: Result) => {
if (result.code !== ResultCode.OK) {
state = 'idle';
bootTask = null;
reject(new Error(`WPS register ${result.code}: ${result.msg ?? ''}`));
return;
}
if (activationSn) {
WPSApi.setWpsFileToken(activationSn);
}
state = 'ready';
resolve();
},
});
});
return bootTask;
}
这段状态机解决三个问题。并发调用复用同一个 bootTask;失败后允许下一次操作重新初始化;成功后再进入文档页会立即返回,不再重新注册。activationSn 作为可选配置传入,调用方不需要在页面中判断是否设置。
三、打开 helper 要区分两类失败
文档服务层先等待门禁,再构造 OpenFileRequest。sendRequest 返回的 Promise 异常与 Result.code 非零并不是同一种错误,应分别记录:
typescript
import { common } from '@kit.AbilityKit';
import { OpenFileRequest, Result, ResultCode, WPSApi } from '@wps/wps_sdk';
import { bootWps } from './WpsGate';
export async function openSandboxFile(options: {
context: common.UIAbilityContext;
path: string;
appKey: string;
appSecret: string;
}): Promise<Result> {
await bootWps(options.appKey, options.appSecret);
const request = new OpenFileRequest(options.context, options.path);
request.enableEdit = false;
try {
const result = await WPSApi.sendRequest(request);
if (result.code !== ResultCode.OK) {
console.error('[WPS] request non-ok', result.code, result.msg);
}
return result;
} catch (error) {
console.error('[WPS] request exception', error);
throw error;
}
}
如果 Promise 直接 reject,优先回到注册状态检查;如果返回了 Result 但 code 非零,才继续检查文件路径、请求参数或文档能力配置。日志中固定 register 与 request 两个前缀,可以避免两类错误混在一起。
四、序列号配置要收口
不同交付形态对激活序列号的要求并不相同。SDK 提供 SdkConstants.isPersonalSdk() 用于运行时判断,但业务页面不应直接使用这个分支。序列号属于初始化配置,应该在注册成功回调中一次完成。
| 交付形态 | 注册 | 激活序列号 |
|---|---|---|
| 需要 WPS 产品授权 | registerApp 成功 |
在成功回调中调用 setWpsFileToken |
| 轻量打开形态 | registerApp 成功 |
不需要设置 |
还要清理历史写法中的 OpenFileRequest.wpsToken。如果全局序列号和请求字段同时存在,排查会变得困难。统一的规则是:序列号只在应用初始化层设置,文档服务层不感知。
五、1013 的诊断顺序
1013 对应 ERROR_CODE_AUTH_FAILURE,含义是接入凭据校验失败。遇到它时,不要先修改 enableEdit 或水印参数,而应先建立一张身份对照表:
- 运行时 Bundle 名称,通过
bundleManager打印。 - 申请凭据时提交的包名。
- 当前 HAR 文件名与交付批次。
registerApp返回的完整code与msg。- 凭据是否过期,近期是否更换签名或包名。
如果是空 key 或空 secret,通常表现为 ResultCode.ERROR,应检查构建配置是否注入了编译期常量;如果是 1013,重点则是身份不一致。两类问题都不应通过反复重启应用来碰运气。
六、升级 HAR 时的回归方式
HAR 批次升级会改变 SDK 内部实现,但对外调用顺序仍应保持一致。建议每次替换依赖后执行同一条回归链:
- clean 后重新执行
ohpm install,确认 import 都来自@wps/wps_sdk。 - 冷启动应用,记录一次注册成功日志。
- 验证需要序列号的交付形态已在回调中完成设置。
- 用沙箱文件完成只读打开,再验证可编辑模式。
- 检查旧代码是否仍在使用
request.wpsToken。
把注册、打开和页面状态分开后,每次升级只需要回归两个模块。对团队协作来说,这比在全仓搜索散落的 registerApp 更可靠。鸿蒙 WPS 二开真正需要长期维护的,不是某一次调用成功,而是这套可观察、可重试、不会越过门禁的初始化结构。
基于 WPS Open SDK 鸿蒙版对接实践整理,仅供开发者参考。 官方对接文档:365.kdocs.cn/l/clQl5cek2... 技术交流 QQ 群:628436767