Cocos Creator 3.8.6 × HarmonyOS NEXT:JS 调 ArkTS 那条"命名规约桥"到底怎么搭才不翻车
做 Creator 3.8.6 迁鸿蒙的朋友,十有八九会在同一件事上卡住:JS 脚本里想调一个 ArkTS 的原生方法,文档说"复用跨平台调原生静态方法的通用桥接接口",但落地时才发现——方法名怎么写、ArkTS 那边怎么暴露、参数为啥到了对岸变成了 undefined——全靠几条不太显眼的命名规则撑着。 规则不摸透,桥搭上了也传不了货。
一、先想通一件事:这条"桥"为什么非得靠命名规约吃饭?
Cocos Creator 3.x 的 JS 运行时(在 NEXT 上本质是 ArkCompiler 的方舟 JS 引擎)跑在引擎的 C++ 核心之上,C++ 再通过 NAPI 跟 ArkTS 侧通信。
所以 JS → ArkTS 的调用链路,抽象出来长这样:
你的游戏 JS 代码
→ Creator 的原生桥接封装(参数序列化)
→ C++ JSB / NAPI 层(按"类名.方法名"去查找导出符号)
→ ArkTS 侧绑定的原生模块(exports 出来的方法)关键点来了——NAPI 到 ArkTS 这个边界,不是反射天堂,它更像一个严格的注册表。
ArkTS 模块在注册到 NAPI 时,会把自己暴露的方法放进一个有限的结构里(可以理解为一张 string → function 的查找表)。桥那头(C++)拿到你传过来的 "类名" + "方法名",就去表里做字符串匹配,匹配上了才 call。
这套机制在 iOS/Android 上是 className + methodSignature(ObjC selector / Java fully-qualified),在 HarmonyOS NEXT 上本质一致:靠字符串规约定位目标函数。
所以 Cocos 3.8.6 说的"核心规则围绕前缀命名和方法名规范展开",翻译成人话就是:
ArkTS 那边暴露的方法,必须能被桥用 [类名]_[方法名] 拼出来的 key 找到;JS 这边传参时,复杂数据走 JSON 字符串,不要指望它自动拆装箱。二、一次 JS → ArkTS 调用的完整旅程
你盯着这个图看十秒就会发现:90% 的坑不在 JS 也不在 ArkTS 逻辑,而在两个隘口——序列化方式 + lookup key 拼法。名字拼错一个下划线,或者参数没串成字符串,调用就静默蒸发了。
三、ArkTS 侧:要"暴露什么"、怎么暴露才符合桥的规矩
Creator 3.8.6 的鸿蒙适配体系里,ArkTS 原生模块一般不是随便找个 .ets 写个函数就行的,它需要被放到 工程约定的原生目录结构 里,通常是:
native/
ohos/
entry/
src/main/ets/
cocosBridge/
GameBridge.ets ← 你暴露给 JS 调用的"类"
index.ets ← 注册出口3.1 核心写法:函数要"可被字符串找到"
最常见、最稳的写法:把方法挂到一个对象上,导出该对象,然后在 NAPI 注册时让桥认出来。
Cocos 的惯例是把"类名"当命名空间前缀,函数名用 _ 拼接——所以你看到的典型风格是这样的:
// native/ohos/entry/src/main/ets/cocosBridge/GameBridge.ets
// 这个类不是真的 TS class,而是"桥认知的命名空间对象"
export const GameBridge = {
// ── 规则:桥会按 "GameBridge_showToast" 来找这个方法 ──
// className = "GameBridge"
// methodName = "showToast"
// 实际 key = "GameBridge_showToast"
GameBridge_showToast(paramsStr: string): string {
try {
const p = JSON.parse(paramsStr || '{}');
// 调用鸿蒙系统能力
console.info(`[GameBridge] toast: ${p.text}`);
// 真机上你可以用 @kit.ArkUI 的 promptAction
// 这里先做 console 版,确保桥先通
return JSON.stringify({ ok: true });
} catch (e) {
return JSON.stringify({ ok: false, error: String(e) });
}
},
// 第二个方法:读设备信息
GameBridge_getDeviceLabel(paramsStr: string): string {
const ctx = getContext(this);
const label = `${ctx.applicationInfo.name ?? 'game'}@${ctx.applicationInfo.versionName ?? '?'}`;
return JSON.stringify({ label });
},
};
// index.ets —— 需要被 bridge 注册进 NAPI 的导出点
export default GameBridge;这个写法里最关键的心智模型:
你在 ArkTS 里不是写class GameBridge { showToast() {} }然后用new。
桥那边的 NAPI 查找逻辑是字符串 → function,所以函数必须作为对象的直接属性存在,且属性名必须匹配ClassName_MethodName。
这就是为什么文档强调"前缀命名和方法名规范"——它不是一个风格建议,是桥的寻址协议。
四、JS 侧:Creator 里怎么"伸手"调到上面那个 toast
Creator 3.8.6 在 NEXT 上的 JS→原生桥,通常走下面这种调用式(不同分支可能包装略有不同,但语义一致):
// assets/scripts/native/Bridge.ts
export class NativeBridge {
// 通用静态调用封装
static call(className: string, methodName: string, params: any = null): string | undefined {
// 注意:参数必须序列化成字符串传过去(NAPI 边界的可靠交集)
const paramStr = params !== null ? JSON.stringify(params) : '{}';
// Cocos Creator 3.x 在 NEXT 的桥入口(具体名称以你用的 3.8.6 分支实际暴露为准)
const ret = (jsb ?? globalThis).jsCallStaticMethod?.(
className,
methodName,
paramStr // ← 通常就这一个参数位:JSON 字符串
);
return typeof ret === 'string' ? ret : undefined;
}
// 业务方法:吐司
static toast(text: string) {
return this.call('GameBridge', 'showToast', { text });
}
// 业务方法:拿设备标签
static getDeviceLabel(): string | undefined {
const r = this.call('GameBridge', 'getDeviceLabel', {});
try { return JSON.parse(r ?? '{}').label; } catch { return undefined; }
}
}调用处就很干净了:
// 某个按钮回调里
NativeBridge.toast('HarmonyOS NEXT 桥接 OK 🎉');
console.log('device:', NativeBridge.getDeviceLabel());五、你一定会踩的"差异"——为什么有的项目写着像、跑着就不通
差异 1:class Foo { bar() } 看起来对,但桥找不到
有人会写:
// 这种写法在桥的字符串查找体系里很危险
export class GameBridge {
showToast(p: string) {}
}问题是:类方法在 ArkTS/TS 编译后不一定是 GameBridge.showToast 这种 flat key,而且桥那边的 NAPI 注册表是按 "GameBridge_showToast" 做字符串匹配,不是走 Reflect.get(instance, 'showToast')。
所以最稳就是我上面给的那种 plain object + ClassName_MethodName 显式拼接 的写法——丑一点,但桥认。
差异 2:参数传对象 { text: 'hi' } 而不是 JSON 字符串
桥的 NAPI 边界通常只担保基本值(number/string)稳定穿越。你直接塞对象:
//很可能在对岸变成 "[object Object]" 或 undefined
jsb.jsCallStaticMethod('GameBridge','showToast', { text:'hi' });正确做法永远是:你来控制序列化:
jsb.jsCallStaticMethod('GameBridge','showToast', JSON.stringify({ text:'hi' }));ArkTS 侧对应地 JSON.parse(paramsStr)。
差异 3:调试时看不到报错,只看到"没反应"
因为 NAPI 侧找不到 key 时,很多实现会选择 fail silent / return undefined,而不是抛红字异常——毕竟游戏运行时不能因为一个统计事件桥断掉就白屏。
排查口诀:先看 ArkTS 的方法名是不是 ClassName_MethodName 拼法;再看 JS 传的 className / methodName 拼写;最后确认 JSON.stringify 没炸(比如参数里含循环引用)。
六、小拓展
目前 Creator 3.8.6 的桥是"稳定可用"状态;API 22 如果动这块,大概率不是废除它,而是收紧边界 + 更严格类型。你需要提前守三条线:
1. 别依赖 jsb 的全局隐式存在当保险箱
API 22 的 ArkCompiler/运行时可能更 aggressively 清理或重排全局命名(尤其如果未来走 ComponentV2 或更强的沙箱化)。
你现在的封装里加一行能力嗅探,以后就不用地毯式改:
// Bridge.ts (JS侧)
private static get bridgeAPI(): any {
return (globalThis as any).jsb ?? (globalThis as any).nativeBridge ?? null;
}2. ArkTS 侧别用花哨的动态 Proxy / 运行时挂属性
API 22 的 ArkTS 对对象形状追踪会更严(趋向更静态可分析)。你现在的 const GameBridge = { GameBridge_foo(){} } 这种 plain object 反而是最抗未来的形态;别想着"优雅反射自动注册"去赌。
3. NAPI 模块路径/注册方式可能标准化成官方模板
如果你现在用的是 Creator 3.8.6 生成的 entry/ets 模板,它未来大概率只会被官方模板升级推动你同步(而不是破坏性删除重来)。守的规矩就是:桥的契约 = ClassName_MethodName key + JSON 字符串传参——这条不变,你的 JS 调用代码基本不用动;ArkTS 侧最多跟着模板对齐目录/导出写法。
七、总结一下下
Cocos Creator 3.8.6 在 HarmonyOS NEXT 上 JS→ArkTS 的调用,说穿了就是一个靠字符串寻址的 NAPI 导出表 + 一个靠 JSON 字符串过边界的序列化约定。
你只要守住两条纪律——ArkTS 暴露的方法名严格遵守 ClassName_MethodName、JS 传参永远自己 JSON.stringify 并且对面 JSON.parse——这条桥就会非常老实、非常好排查、也最能扛未来的 API 升级。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。