Skip to content

Stage06|JSB & Native Bridge ​

Lesson080|阶段复习:从 Creator API 定位到原生实现 ​

Tags: #Creator2.x #Stage06 #JSB #Native #SourceCodeDifficulty: ⭐⭐⭐⭐⭐


阶段挑战:从一个公开 API 走到可解释的 Native 故障 ​

本阶段的最终能力不是背 JSB 术语,而是面对:

text
Web 正常、Native 失败
Native 偶发崩溃
跨边界调用过多
回调晚于页面生命周期
后台线程直接更新 UI

你能否从公开 Creator API 出发,画出执行链、确定线程/所有权、用最小实验和目标平台日志验证,而不是猜某个 C++ 文件。

一、本课目标 ​

串联 Lesson073~Lesson079,完成 Cocos 公共核心的最后一段:

text
Creator API
→ JavaScript Engine
→ JSB / Binding
→ C++ Engine
→ Platform / GPU

完成本课后,你应该能够对一个 Web/Native 差异、JSB 性能问题或原生崩溃建立分层排查路径。


二、Stage06 知识树 ​

text
运行环境
├── TypeScript / JavaScript
├── Browser JavaScript Engine
└── Native Embedded JavaScript Engine

跨语言桥梁
├── Binding 注册
├── 参数和返回值转换
├── JS / C++ 对象映射
└── 异常和回调

生命周期
├── JavaScript GC
├── C++ 所有权 / 引用计数
├── Proxy 有效性
└── 异步任务

平台边界
├── 文件、音频、网络
├── 渲染和 GPU
├── 主线程与工作线程
└── Web / Native 差异

三、从 API 到原生实现的标准流程 ​

text
1. 锁定 Creator 版本、平台和构建模式
2. 找公开 API 定义
3. 追踪 JS 包装、setter 或适配层
4. 确认是否进入 Binding
5. 检查参数转换和对象映射
6. 找到 C++ Engine 方法
7. 找到最终平台实现
8. 追踪返回值、异常和回调线程
9. 检查两边对象生命周期
10. 用断点、日志和最小实验验证

四、综合案例:Native 保存截图 ​

业务目标:

text
截取当前画面
→ 保存文件
→ 返回路径
→ UI 显示成功

可能链路:

text
JS 发起请求
→ Renderer / GPU 完成读回
→ Native 获取像素或文件数据
→ 工作线程编码图片
→ 平台文件系统保存
→ 投递主线程
→ Binding 转换路径和错误
→ JS 检查页面仍有效
→ 更新 UI

需要检查:

  • GPU 读回是否造成同步卡顿。
  • 像素缓冲的内存峰值。
  • 编码是否阻塞主线程。
  • 写入路径是否可用。
  • 回调是否来自正确线程。
  • 页面关闭后回调如何处理。
  • Native 和 JS 对象是否正确释放。

一个功能可能同时跨越 Renderer、Memory、IO、JSB、Thread 和 Lifecycle。


五、综合案例:Web 正常,Native 崩溃 ​

排查树:

text
是否只在 Native 出现?
├── 是:检查平台分支和 Binding
│   ├── 参数类型是否一致
│   ├── Native 对象是否已销毁
│   ├── 回调线程是否正确
│   ├── 文件/权限是否不同
│   └── 是否有 C++ 崩溃堆栈
└── 否:先查公共业务和资源逻辑

不要因为崩溃发生在 Native 就直接认定是 C++ 引擎 Bug;错误参数、过期代理和业务生命周期同样可能触发原生崩溃。


六、综合性能判断 ​

看到 Binding 函数耗时时,应问:

text
调用了多少次?
每次传多少数据?
是否发生数组或字符串复制?
是否能批处理?
是否在目标设备复现?
优化后状态是否仍一致?

跨 JSB 不是天然错误,无法解释调用规模的“JSB 慢”也不是有效结论。


七、阶段综合练习 ​

问题:Native 下载器完成后偶发崩溃,通常发生在用户快速关闭下载页面时。

请从以下层次分析:

text
JavaScript 页面生命周期
JS callback 保存与释放
Binding 对象映射
C++ 下载任务生命周期
工作线程到脚本线程切换
请求取消和过期结果

参考分析 ​

text
页面发起任务并保存 requestId
→ Native 保存回调并启动下载
→ 页面退出,取消或使 requestId 失效
→ Native 完成后不直接从工作线程调用 JS
→ 投递脚本线程
→ 检查 ScriptEngine、callback、页面和 requestId
→ 有效才交付结果
→ 无论成功、失败、取消都释放 callback 和对象映射

八、Stage06 面试题 ​

1. JSB 是什么? ​

JSB 是 JavaScript 与 C++ 之间的绑定层,负责函数注册、参数转换、对象映射、返回值和生命周期协调。

2. 为什么频繁 JSB 调用可能较贵? ​

因为每次可能包含边界跳转、类型检查、对象查找、数据转换和复制,总成本会随次数和数据量累积。

3. JS 对象被 GC 后 C++ 对象一定销毁吗? ​

不一定,两边由不同生命周期模型管理,需要绑定层和所有权策略协调。

4. 为什么 Native 线程不能随意调用 JS? ​

JavaScript Engine 通常要求在指定线程和正确上下文执行,错误线程会导致竞态和崩溃。

5. Web 正常、Native 异常应如何排查? ​

从平台适配、绑定转换、Native 对象、线程、文件权限、图形 API 和生命周期差异逐层检查。

6. 如何追踪一个 Creator API? ​

锁定版本和平台,从公开 API 进入包装、适配、Binding、C++ 和平台实现,再追踪返回和生命周期。


阶段综合实验:NativeCapabilityLab ​

使用项目已有的 Native 能力与 Creator 2.4.x 构建流程。不要为了课程手工修改引擎源码、生成绑定文件或伪造原生资源。

实验模块 ​

text
RuntimeProbe:Web/Native 环境
APITrace:公开入口时间线
Capability:文件/音频/网络最小调用
Lifecycle:取消与 session
Thread:后台结果回主线程
Batch:逐项与批量边界

统一日志格式 ​

ts
function nativeTrace(
    name: string,
    stage: string,
    session: string,
    extra?: any
) {
    cc.log(
        '[NATIVE-TRACE]',
        name,
        stage,
        'session=', session,
        extra || ''
    );
}

每条记录至少有:

text
api
stage(submit/callback/error/cancel)
session
timestamp
platform
thread(若可取得)

综合步骤 ​

  1. Web 运行,记录能力结果。
  2. Native 构建运行,记录同一动作。
  3. 从公开 API 找到 Runtime 封装。
  4. 用固定版本源码搜索绑定注册。
  5. 记录参数转换和回调方向。
  6. 关闭页面/切账号,验证旧 session 结果被拒绝。
  7. 测 1/100/1000 次调用与批量方案。
  8. 高并发后台回调,确认主线程队列有上限。

综合案例:Native 保存截图 ​

需求拆解 ​

text
Renderer 生成截图数据
→ 选择 Web/Native 存储能力
→ 可能有大字节复制
→ 后台写入
→ 回调成功/失败
→ 页面显示结果

风险 ​

  • 截图数据过大造成复制和峰值;
  • Native 文件路径与 Web URL 不同;
  • 写入线程不能阻塞渲染;
  • 页面关闭后回调失效;
  • 用户权限/沙盒;
  • 同名写入竞态。

验证 ​

小图、中图、大图分别测;Web/Native 冷缓存测;关闭页面/退出账号测;记录 submit 到 callback、内存峰值和最终文件语义。

不要把“保存成功”定义为回调已提交;必须确认平台写入完成和下一次读取。

阶段故障定位树 ​

text
API 不存在
→ 平台/版本/构建能力

API 返回错误
→ 参数/权限/路径/资源/平台状态

回调不回来
→ 线程/生命周期/取消/网络/队列

回调回来但崩溃
→ Native handle/线程归属/ABI/旧代理

功能正确但慢
→ JSB 调用频率/复制/批量/主线程提交

Web/Native 不一致
→ 能力适配/资源布局/安全策略/生命周期

阶段综合练习与答案 ​

练习一 ​

每帧读取 1000 个 Native Node 的位置,如何改造?

答案: 先用纯 JS/模拟/真实 Native 对照证明边界成本;减少读取对象,只同步变化/可见对象;优先批量 API 或 Native 侧计算,按帧率降频;验证坐标时序与视觉。

练习二 ​

页面关闭后 Native 下载完成回调仍返回,怎样保证安全?

答案: 关闭时取消底层任务(若支持),递增 generation/session;回调只提交到主线程;提交前验证 session、Node/代理有效性和资源所有权。

练习三 ​

Web 保存设置成功,Native 失败,排查顺序?

答案: 确认平台环境与能力实现;输出最终沙盒路径/权限错误;检查构建配置和 Native API;比较同步/异步完成;加回退与错误提示,不使用 Web 绝对路径。

练习四 ​

Native 回调每秒 5000 条,UI 只需显示进度,如何设计?

答案: Native/后台聚合,主线程按显示频率采样最后值;队列有上限和 session;UI 只在可见且值跨阈值时刷新,记录丢弃策略。

练习五 ​

为什么不能仅靠 JS GC 解决 Native 泄漏?

答案: Native 可能由引用计数、回调、线程或平台句柄持有;JS 不可达不代表 Native owner 归还。必须明确跨边界释放协议。

Stage06 能力验收 ​

你应该能够:

  1. 画出 Web/Native/JSB/C++/平台分层。
  2. 从公开 API 建立源码调用链证据表。
  3. 区分 JS Proxy、Native Object、Platform Handle。
  4. 估算跨 JSB 调用的次数、复制和批量收益。
  5. 设计 GC 与 Native 引用计数的断环路径。
  6. 为 Web/Native 能力建立适配层和回退。
  7. 保证后台回调回到正确线程与 session。
  8. 形成带版本、设备、日志和错误路径的 Native 故障报告。

版本边界与官方入口 ​

Stage06 以 Creator 2.4.x Native 工作流为主。绑定注册、JS Engine、Cocos2d-x 和平台线程实现必须固定源码/构建版本;不能把某一平台观察推广为全部 Native。

九、Stage06 总结 ​

text
JavaScript Engine 执行业务脚本。
JSB 连接 JavaScript 和 C++。
对象绑定包含身份、转换和生命周期。
跨边界成本取决于次数和数据量。
Web 与 Native 有不同运行环境。
后台任务必须安全交付到脚本线程。
源码定位必须绑定版本、平台和构建模式。

Stage06 完成后,你应该从“Creator API 使用者”进一步成为:

能沿着 JavaScript、Binding、C++ 和平台层建立证据链的开发者。


十、下一阶段预告 ​

Stage06 完成,下一阶段进入:

text
Stage07|Modern C++ Foundation
Lesson081|从 .cpp 到可执行文件:预处理、编译与链接

下一阶段将用 Modern C++ 补齐阅读 Native 引擎和服务端代码需要的语言、内存、所有权、并发与工程基础。