典型痛点:

  • 想通过桥接一次性传“大对象”(复杂 JSON、列表、图片 Base64),结果性能骤降或直接 OOM。
  • Web 与原生对消息结构理解不一致,导致解析错误、字段丢失或版本不兼容。
  • 随业务演进不断给消息加字段,旧版本 JS/原生无法兼容。

本文从消息结构设计与序列化入手,结合本项目中 ArkTsAttribute、插件参数等实践,给出一套可落地的设计与排错方案。代码约占 3/10,其余用文字解释思路。


1. 双向桥接中的“消息”究竟长什么样

在本项目中,常见的桥接消息有两类:

  • ArkTS → JS:

    • 使用 onArkTsResult(JSON.stringify(attr), 'CoreHarmony', '') 发送:

      interface ArkTsAttribute {
        content: string;   // 事件名,例如 'resume'、'perf'
        result: ESObject[]; // 参数数组,例如 [{fps: 60, mem: 123}]
      }
      
  • JS → ArkTS(插件参数):

    • 使用 cordova.exec(success, fail, service, action, [args]) 发送:

      cordova.exec(success, fail, 'GamePlugin', 'toast', [{ message: 'Hi' }]);
      

1.1 消息流动示意图

渲染错误: Mermaid 渲染失败: Parse error on line 12: ...ArkTS A1[execute(action, args)] ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

设计得当时,这些“消息”应该满足:

  • 结构稳定、可演化;
  • 数据量可控,避免一次传太多;
  • 双端解析简单、不容易踩坑。

2. 大对象直接塞进桥接的风险

2.1 性能与内存问题

  • 序列化开销:
    • 大对象需要 JSON 序列化(ArkTS/JS 端),会占用 CPU;
  • 内存占用:
    • 传递多层嵌套对象、长字符串(尤其是图片 Base64)时,桥接层和两端同时持有多份数据副本;
  • 带宽限制:
    • 在线资源场景中,大 JSON/长字符串还会占用网络带宽。

2.2 压力示意

渲染错误: Mermaid 渲染失败: Parse error on line 2: flowchart TD A[大对象(JSON/大数组/Base64图片)] -------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

如果每一层都对同一份数据做拷贝/解析,很容易形成性能瓶颈甚至内存峰值过高。


3. 消息结构设计的实践建议

3.1 使用“轻消息 + 重数据”的模式

不要在桥接消息里塞入全部数据本体,而是:

  • 在消息中传递:
    • ID、类型、版本号、小段摘要;
  • 重数据(大数组/二进制)存放在:
    • 本地缓存文件或数据库;
    • 远端接口,由对方按需获取。

示例:原生采集性能日志,不要一次把几千条数据通过 onArkTsResult 推给 JS,而是:

  • 消息中只告诉 JS:

    {
      "content": "perf_snapshot",
      "result": [{"id": "snap-2024-03-21-001"}]
    }
    
  • JS 收到后再根据 id 调用接口或本地缓存获取详细数据。

3.2 设计可扩展的 JSON 结构

约定:

  • 固定字段:type / version / payload
  • payload 内按业务模块划分子字段;
  • 新增字段时保证向后兼容——旧版本忽略未知字段不会导致错误。

示例模板

{
  "type": "perf",        // 消息类型
  "version": 1,           // 协议版本
  "payload": {
    "fps": 60,
    "memory": 123,
    "extra": {            // 预留扩展
      "device": "xxx",
      "scene": "home"
    }
  }
}

4. ArkTS → JS 消息:onArkTsResult 的设计与排查

4.1 统一封装消息构造

不要到处手写 content + result,推荐集中封装:

function sendToJs(content: string, payload: ESObject): void {
  const attr: ArkTsAttribute = { content, result: [payload] };
  try {
    cordova.onArkTsResult(JSON.stringify(attr), 'CoreHarmony', '');
  } catch (e) {
    console.error('[Bridge] sendToJs error:', e);
  }
}

// 使用示例
sendToJs('perf', { fps: 60, memory: 123 });

好处

  • 统一控制 JSON 结构;
  • 出错时日志集中,便于排查。

4.2 常见问题

  • 消息体字段改名,JS 侧代码仍使用旧字段名;
  • JSON.stringify 抛异常(循环引用、大对象超限等);
  • content 与 Core 期望的事件名不匹配。

排查方法

  • 先在 ArkTS 侧打印 JSON 字符串,再在 JS 侧打印收到的原始消息,两边对比;
  • 出问题时优先判断:是“收不到”还是“收到但解析失败”。

5. JS → ArkTS 消息:插件参数设计与大对象处理

5.1 推荐参数格式

cordova.exec 中传入参数时,建议使用统一的对象结构,而不是随意拼数组:

cordova.exec(
  success,
  fail,
  'GamePlugin',
  'doSomething',
  [{
    type: 'saveSettings',
    version: 1,
    payload: {
      enableSound: true,
      difficulty: 'hard'
    }
  }]
);

ArkTS 侧解析示意:

execute(action: string, args: ESObject[], cb: CallbackContext): boolean {
  if (action === 'doSomething') {
    const msg = args?.[0] as ESObject;
    const type = msg['type'];
    const payload = msg['payload'] as ESObject;
    // 基于 type/payload 做分发
    // ...
    cb.success();
    return true;
  }
  return false;
}

5.2 大对象参数的替代策略

不要这样做:

// ❌ 直接把庞大配置/日志全部塞进去
cordova.exec(success, fail, 'GamePlugin', 'uploadLog', [{ bigLogBlob: veryLargeString }]);

推荐做法:

  • 使用“ID + 分片”模式:
    • 先把大对象分片写入本地(如 Web 侧 IndexedDB、原生沙箱文件),再通过桥接传递 id 和元数据;
    • ArkTS/JS 根据 id 分步获取或处理数据。

6. 典型问题场景与解决思路

场景 1:偶发 JSON.parse 失败,提示 Unexpected token

现象

  • JS 侧在处理 onArkTsResult 消息时抛出 JSON.parse 异常,错误内容为 Unexpected token

可能原因

  • ArkTS 侧构造 JSON 时拼接了额外的日志/前缀,导致非纯 JSON;
  • 消息体中包含非 UTF-8 可打印字符或被截断。

处理建议

  • 保证 onArkTsResult 只传递纯 JSON 字符串;

  • 使用严格的构造函数,而不是字符串拼接:

    const json = JSON.stringify(attr); // 避免手工拼接
    
    
  • 在异常发生时打印原始字符串样例,结合长度检查是否被截断。

场景 2:同一个消息结构在不同版本 JS/原生间不兼容

现象

  • 新版本 ArkTS 增加了字段或修改了字段名;
  • 旧版本 JS 无法正确解析或直接报错。

解决思路

  • 在消息中加入 version 字段;

  • JS 侧根据 version 做兼容性处理:

    function handlePerfMessage(msg) {
      const v = msg.version || 1;
      if (v === 1) {
        // 旧版本结构
      } else if (v === 2) {
        // 新版本结构
      }
    }
    
    
  • 尽量避免删除/重命名字段,更多使用“新增字段 + 默认值”的方式演进。


7. 综合排查流程图

当你在桥接过程中遇到“消息相关”的问题(解析失败、数据丢失、性能异常等),可以按以下流程排查:

ArkTS→JS

JS→ArkTS

桥接消息异常

问题发生在 ArkTS→JS 还是 JS→ArkTS?

打印 ArkTS 侧 JSON.stringify 结果

JS 能收到原始字符串吗?

检查 onArkTsResult 调用与 Core 通路

检查 JS JSON.parse/字段名/版本兼容

打印 JS 调用参数与 插件 execute 日志

ArkTS 能解析 args 吗?

检查参数结构、类型转换、字段名

关注业务逻辑/大对象处理

消息是否包含大对象?

考虑轻消息+重数据、分片/ID 方案


8. 小结

  • 双向桥接中的“消息”设计,是混合架构可维护性的关键:
    • 结构要稳定、可演进:type/version/payload 三段式结构是常用实践;
    • 数据量要可控:大对象尽量转为“引用 + 分片”模型;
    • 编码要显式、集中:统一的构造/解析函数比到处手写 JSON 更安全。
  • 当遇到解析失败或性能问题时,不要只在一端加 try/catch,而要沿着“ArkTS → Core → JS”或反向链路,把每一层的原始消息打印出来,对比差异,这样才能稳稳地把问题抓出来。

欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐