在移动应用开发中,越来越多的团队选择将 Cocos Creator 作为跨平台游戏/动画引擎嵌入原生 App(如电商互动、营销小游戏、虚拟展厅等)。此时,业务层(登录、支付、分享、数据上报等)往往分布在原生端和 Cocos 两端,如何实现稳定、高效的双向通信,是保证项目顺利落地的关键。本文基于实际项目经验,系统讲解 Cocos Creator(TypeScript)与原生平台(Android/iOS)的业务层交互方案,并提供可直接落地的代码实践。
一、背景与需求
典型混合架构中,原生 App 负责提供系统能力、账号体系、支付渠道等核心业务,Cocos 负责渲染互动场景、执行游戏逻辑。交互场景通常包括:
- Cocos 调用原生:获取设备信息、调用分享/支付、请求网络数据、触发震动等。
- 原生调用 Cocos:用户登录成功后通知 Cocos 更新 UI、推送消息到达、支付回调结果等。
因此需要一套双向、异步、可靠的通信机制,同时兼顾易用性与可维护性。
二、技术选型与通信原理
2.1 Cocos Creator 与原生通信基础
Cocos Creator 底层基于 JavaScriptCore(iOS)或 V8(Android)执行 JS/TS 代码,原生代码可以通过 JSB(JavaScript Binding)桥接调用 JS 函数,反之 JS 也能通过反射或桥接模块调用原生方法。
| 版本 | 原生调用 JS 方式 | JS 调用原生方式 |
|---|---|---|
| Cocos Creator 2.x | CocosJavascriptJavaBridge.evalString (Android)[[AppController share] sendEventToCocos] (iOS) | jsb.reflection.callStaticMethod |
| Cocos Creator 3.x | JsbBridge.sendToScript | JsbBridge.sendToNative 或 jsb.reflection(兼容) |
推荐使用
JsbBridge(Cocos 3.6+ 内置,低版本可手动移植),它提供了统一的双向字符串传递接口,便于封装成事件系统。
2.2 设计原则
- 协议统一:所有通信消息采用 JSON 字符串,包含
cmd(命令名)、data(载荷)、callbackId(可选,用于异步回调)。 - 事件驱动:原生和 Cocos 分别维护事件监听器,通过
on/off/emit模式解耦业务逻辑。 - 线程安全:原生调用 Cocos 必须在主线程(UI 线程)执行;Cocos 调用原生可以异步执行耗时操作。
三、统一通信接口设计
我们在 TypeScript 层封装一个 NativeBridge 单例,对外提供 callNative(method, params) 和 onNativeEvent(eventName, callback) 两个核心 API。
// NativeBridge.ts
interface CallbackEntry {
resolve: (data: any) => void;
reject: (error: any) => void;
timeoutId: number;
}
export class NativeBridge {
private static instance: NativeBridge;
private eventHandlers: Map<string, ((data: any) => void)[]> = new Map();
private pendingCalls: Map<string, CallbackEntry> = new Map();
private callbackId = 0;
static getInstance(): NativeBridge {
if (!this.instance) this.instance = new NativeBridge();
return this.instance;
}
// Cocos 调用原生(支持 Promise 异步回调)
callNative(method: string, params?: any, timeout = 30000): Promise<any> {
return new Promise((resolve, reject) => {
const callbackId = `${method}_${Date.now()}_${this.callbackId++}`;
const timeoutId = setTimeout(() => {
this.pendingCalls.delete(callbackId);
reject(new Error(`Call native method ${method} timeout`));
}, timeout);
this.pendingCalls.set(callbackId, { resolve, reject, timeoutId });
const message = JSON.stringify({
cmd: method,
data: params,
callbackId,
});
if (typeof (window as any).JsbBridge?.sendToNative === 'function') {
(window as any).JsbBridge.sendToNative(message);
} else if (typeof (window as any).jsb?.reflection?.callStaticMethod === 'function') {
// 降级方案
(window as any).jsb.reflection.callStaticMethod(
'com/example/NativeHelper',
'call',
'(Ljava/lang/String;)V',
message
);
} else {
reject(new Error('Native bridge not available'));
}
});
}
// 原生调用 Cocos 时触发的事件监听
onNativeEvent(eventName: string, callback: (data: any) => void): void {
if (!this.eventHandlers.has(eventName)) {
this.eventHandlers.set(eventName, []);
}
this.eventHandlers.get(eventName)!.push(callback);
}
offNativeEvent(eventName: string, callback?: (data: any) => void): void {
if (!callback) {
this.eventHandlers.delete(eventName);
return;
}
const handlers = this.eventHandlers.get(eventName);
if (handlers) {
const index = handlers.indexOf(callback);
if (index !== -1) handlers.splice(index, 1);
}
}
// 由原生调用,分发事件或回调
public onNativeMessage(rawMessage: string): void {
try {
const msg = JSON.parse(rawMessage);
if (msg.type === 'event') {
const handlers = this.eventHandlers.get(msg.event);
if (handlers) {
handlers.forEach(handler => handler(msg.data));
}
} else if (msg.type === 'response') {
const pending = this.pendingCalls.get(msg.callbackId);
if (pending) {
clearTimeout(pending.timeoutId);
if (msg.error) pending.reject(msg.error);
else pending.resolve(msg.data);
this.pendingCalls.delete(msg.callbackId);
}
}
} catch (e) {
console.error('[NativeBridge] parse error', e);
}
}
}
四、Cocos 端业务使用示例
4.1 调用原生支付接口
import { NativeBridge } from './NativeBridge';
async function pay(productId: string): Promise<void> {
try {
const result = await NativeBridge.getInstance().callNative('pay', {
productId,
amount: 6.99,
});
console.log('支付成功', result);
// 更新游戏钻石
} catch (error) {
console.error('支付失败', error);
// 提示用户失败
}
}
4.2 监听原生登录事件
// 在场景启动时注册监听
NativeBridge.getInstance().onNativeEvent('userLogin', (userInfo) => {
console.log('收到登录信息', userInfo);
// 更新游戏内用户头像、昵称等
});
// 页面销毁时移除监听(避免内存泄漏)
onDestroy() {
NativeBridge.getInstance().offNativeEvent('userLogin');
}
五、原生端实现(Android & iOS)
5.1 Android 实现
5.1.1 初始化 JsbBridge(Cocos 3.x 推荐)
Cocos 3.x 的 JsbBridge 在 Android 端对应 JsbBridge.java,我们在 AppActivity 中初始化回调:
// AppActivity.java
import com.cocos.lib.JsbBridge;
public class AppActivity extends CocosActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置原生消息处理函数,接收 Cocos 发来的消息
JsbBridge.setCallback(new JsbBridge.ICallback() {
@Override
public void onScript(String message) {
handleFromCocos(message);
}
});
}
private void handleFromCocos(String jsonMsg) {
try {
JSONObject obj = new JSONObject(jsonMsg);
String cmd = obj.getString("cmd");
String callbackId = obj.getString("callbackId");
JSONObject data = obj.optJSONObject("data");
switch (cmd) {
case "pay":
// 调用支付 SDK
doPay(data, callbackId);
break;
case "getDeviceInfo":
sendResponse(callbackId, getDeviceInfo());
break;
default:
sendError(callbackId, "unknown cmd");
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void doPay(JSONObject data, String callbackId) {
// 模拟异步支付
new Thread(() -> {
// 支付结果
boolean success = true;
JSONObject result = new JSONObject();
try {
result.put("orderId", "123456");
if (success) {
sendResponse(callbackId, result);
} else {
sendError(callbackId, "pay failed");
}
} catch (Exception e) {}
}).start();
}
private void sendResponse(String callbackId, JSONObject data) {
JSONObject resp = new JSONObject();
try {
resp.put("type", "response");
resp.put("callbackId", callbackId);
resp.put("data", data);
// 必须在主线程调用 Cocos
runOnUiThread(() -> JsbBridge.sendToScript(resp.toString()));
} catch (Exception e) {}
}
private void sendError(String callbackId, String errorMsg) {
JSONObject resp = new JSONObject();
try {
resp.put("type", "response");
resp.put("callbackId", callbackId);
resp.put("error", errorMsg);
runOnUiThread(() -> JsbBridge.sendToScript(resp.toString()));
} catch (Exception e) {}
}
// 原生主动调用 Cocos(例如登录成功后)
private void notifyLoginSuccess(String userId) {
JSONObject msg = new JSONObject();
try {
msg.put("type", "event");
msg.put("event", "userLogin");
msg.put("data", new JSONObject().put("userId", userId));
runOnUiThread(() -> JsbBridge.sendToScript(msg.toString()));
} catch (Exception e) {}
}
}
5.1.2 老版本或 jsb.reflection 方式(兼容)
若无法使用 JsbBridge,可通过 CocosHelper 工具类:
public class CocosHelper {
public static void callCocos(String msg) {
CocosJavascriptJavaBridge.evalString("window.onNativeMessage && window.onNativeMessage('" + escape(msg) + "')");
}
}
注意转义特殊字符。
5.2 iOS 实现
iOS 中 Cocos 引擎通过 CCRuntime 执行 JS,原生可通过 [[CCDirector sharedDirector] getVRoot]->_runtime->executeScript() 调用 JS 函数。更简单的方式是利用 JsbBridge。
5.2.1 使用 JsbBridge
// AppController.mm
#import "platform/ios/CCEAGLView-ios.h"
#import "platform/ios/CCRuntime.h"
#import "jsb_bridge.hpp"
@implementation AppController
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 设置接收 Cocos 消息的回调
se::JsbBridge::setCallback([](const se::JsbBridge::StringType& arg){
NSString *msg = [NSString stringWithUTF8String:arg.c_str()];
[self handleFromCocos:msg];
});
return YES;
}
- (void)handleFromCocos:(NSString *)jsonMsg {
NSData *data = [jsonMsg dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSString *cmd = dict[@"cmd"];
NSString *callbackId = dict[@"callbackId"];
NSDictionary *params = dict[@"data"];
if ([cmd isEqualToString:@"pay"]) {
[self doPay:params callbackId:callbackId];
}
}
- (void)doPay:(NSDictionary *)params callbackId:(NSString *)callbackId {
// 模拟异步
dispatch_async(dispatch_get_global_queue(0, 0), ^{
BOOL success = YES;
NSDictionary *result = @{@"orderId": @"xxx"};
[self sendResponse:callbackId data:result error:success ? nil : @"failed"];
});
}
- (void)sendResponse:(NSString *)callbackId data:(NSDictionary *)data error:(NSString *)error {
NSMutableDictionary *resp = [NSMutableDictionary dictionary];
resp[@"type"] = @"response";
resp[@"callbackId"] = callbackId;
if (error) {
resp[@"error"] = error;
} else {
resp[@"data"] = data;
}
NSError *err;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:resp options:0 error:&err];
if (jsonData) {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
// 必须在主线程调用
dispatch_async(dispatch_get_main_queue(), ^{
se::JsbBridge::sendToScript([jsonString UTF8String]);
});
}
}
// 原生主动触发事件
- (void)onUserLogin:(NSString *)userId {
NSDictionary *event = @{
@"type": @"event",
@"event": @"userLogin",
@"data": @{@"userId": userId}
};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:event options:0 error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
se::JsbBridge::sendToScript([jsonString UTF8String]);
});
}
@end
六、最佳实践与注意事项
6.1 通信协议规范
- 定义清晰的消息清单(如
pay、share、getUserInfo),避免随意扩展。 - 所有回调必须携带
callbackId,超时机制防止永久等待。 - 错误信息应结构化,包含
code和message。
6.2 性能与安全
- 避免频繁大字符串传递:图片、日志等大数据通过文件或数据库共享,消息只传路径。
- 敏感信息加密:如用户 token,避免明文在桥接层泄露。
- 防重放攻击:callbackId 需包含时间戳和随机数。
6.3 生命周期管理
- Cocos 场景切换时,及时移除不需要的事件监听。
- 原生端持有 Activity 上下文时注意弱引用,避免内存泄漏。
- 游戏处于后台时,原生可缓存待发送消息,恢复时再处理。
6.4 调试技巧
- 在 Cocos 端打印所有发送/接收的 JSON 日志,并用
JSON.stringify格式化。 - 原生端使用
Log.d记录桥接消息,方便排查序列化错误。 - 提供 mock 模式,在没有原生环境时用 Web 模拟回调。
6.5 异步处理与线程
- 原生调用
sendToScript必须在主线程(AndroidrunOnUiThread,iOSdispatch_get_main_queue)。 - Cocos 调用原生后,原生耗时操作应在子线程执行,结果再回调回主线程发送。
七、总结
本文介绍了一套基于 JsbBridge 和统一消息协议的原生与 Cocos Creator 业务层交互方案,涵盖接口封装、异步 Promise 化、双端实现及工程实践要点。该方案已在多个生产项目中稳定运行,支持登录、支付、分享等复杂业务场景。