Cocos2d-x JS 调用 Java 完全指南:jsb.reflection 反射机制与跨平台 SDK 集成

引言

在 Cocos2d-x JavaScript(Cocos2d-JS)游戏开发中,构建跨平台游戏是核心优势之一。当游戏需要集成 Android 平台的第三方 SDK(如支付、统计、广告、社交等)时,由于这些 SDK 大多只提供 Java 接口,需要在 JavaScript 层调用 Java 方法。Cocos2d-x 提供了 jsb.reflection.callStaticMethod 反射机制,让 JS 代码可以直接调用 Java 的静态方法。本文将详细介绍这一机制的原理、方法签名规则、常见使用场景以及生产环境中的最佳实践。

架构概述

JS 调用 Java 的数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
┌─────────────────────────────────────────────────────────────────────┐
│ Cocos2d-JS 调用 Java 架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ JavaScript 层 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ jsb.reflection.callStaticMethod( │ │
│ │ "org/cocos2dx/javascript/Test", │ │
│ │ "hello", │ │
│ │ "(Ljava/lang/String;)V", │ │
│ │ "message from JS" │ │
│ │ ); │ │
│ └────────────────────────────┬────────────────────────────────┘ │
│ │ SpiderMonkey JS 引擎 │
│ │ 绑定调用 │
│ ▼ │
│ C++ 中间层 (JniBridge) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ • 解析方法签名 │ │
│ │ • 转换 JS 参数为 JNI 类型 │ │
│ │ • 通过 JNIEnv 调用 Java 方法 │ │
│ └────────────────────────────┬────────────────────────────────┘ │
│ │ JNI (Java Native Interface) │
│ ▼ │
│ Java 层 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ public static void hello(String msg) { │ │
│ │ System.out.println(msg); │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 注意:JS 与 Java 的交互在 Cocos 的 GL 渲染线程中进行 │
│ 如需更新 Android UI,必须在 UI 线程执行 │
│ │
└─────────────────────────────────────────────────────────────────────┘

核心 API:callStaticMethod

方法签名

1
jsb.reflection.callStaticMethod(className, methodName, methodSignature, ...parameters);
参数 类型 说明
className string Java 类的完整路径,使用 / 分隔包名
methodName string 要调用的静态方法名
methodSignature string JNI 方法签名,描述参数和返回值类型
...parameters any 可变参数,对应方法签名中的参数

Java 类型签名对照表

Cocos2d-JS 支持的 Java 类型签名:

Java 类型 JNI 签名 说明
int I 整数
float F 单精度浮点数
boolean Z 布尔值
String Ljava/lang/String; 字符串(注意末尾的分号)

方法签名格式:(参数签名)返回值签名

方法声明 方法签名
void init() ()V
void hello(String msg) (Ljava/lang/String;)V
int sum(int a, int b) (II)I
boolean check(int id, float score) (IF)Z
String getName(int id) (I)Ljava/lang/String;

基础使用示例

Java 端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.cocos2dx.javascript;

public class Test {

/**
* 无参数、无返回值
*/
public static void init() {
System.out.println("SDK initialized");
}

/**
* 字符串参数、无返回值
*/
public static void hello(String msg) {
System.out.println("JS says: " + msg);
}

/**
* 两个 int 参数,返回 int
*/
public static int sum(int a, int b) {
return a + b;
}

/**
* 重载方法:一个 int 参数,返回 int
*/
public static int sum(int a) {
return a + 2;
}

/**
* int + float 参数,返回 boolean
*/
public static boolean checkScore(int id, float score) {
return score >= 60.0f;
}
}

JavaScript 端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 1. 调用无参数方法
jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/Test",
"init",
"()V"
);

// 2. 调用 String 参数方法
jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/Test",
"hello",
"(Ljava/lang/String;)V",
"this is a message from JS"
);

// 3. 调用 int + int,返回 int
var result = jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/Test",
"sum",
"(II)I",
3,
7
);
cc.log("sum(3, 7) = " + result); // 输出: 10

// 4. 调用重载方法(一个 int 参数)
var result2 = jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/Test",
"sum",
"(I)I",
3
);
cc.log("sum(3) = " + result2); // 输出: 5

// 5. 调用 int + float,返回 boolean
var passed = jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/Test",
"checkScore",
"(IF)Z",
1001,
85.5
);
cc.log("Check result: " + passed); // 输出: true

实际应用场景

场景一:支付 SDK 集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Java 端:支付桥接类
package org.cocos2dx.javascript;

public class PayBridge {

private static native void onPayResult(int code, String productId);

public static void pay(String productId, String productName, float price) {
// 调用支付宝/微信支付 SDK
// ...
// 支付完成后回调 JS
onPayResult(0, productId); // 0 表示成功
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// JavaScript 端:调用支付
var PayManager = {
pay: function(productId, productName, price) {
if (!cc.sys.isNative || cc.sys.os !== cc.sys.OS_ANDROID) {
cc.warn("Pay only supported on Android native");
return;
}

jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/PayBridge",
"pay",
"(Ljava/lang/String;Ljava/lang/String;F)V",
productId,
productName,
price
);
}
};

// 注册 Java 回调(需要在 C++ 层实现桥接)
// Java 调用 C++ 的 onPayResult,C++ 再调用 JS 回调

场景二:统计 SDK 事件上报

1
2
3
4
5
6
7
8
9
// Java 端
public class AnalyticsBridge {
public static void logEvent(String eventName, String jsonParams) {
// 调用友盟/AppsFlyer/Firebase 等统计 SDK
// Bundle params = new Bundle();
// 解析 jsonParams 填入 Bundle
// mAnalytics.logEvent(eventName, params);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// JavaScript 端
var Analytics = {
logEvent: function(eventName, params) {
if (cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID) {
var jsonStr = JSON.stringify(params || {});
jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/AnalyticsBridge",
"logEvent",
"(Ljava/lang/String;Ljava/lang/String;)V",
eventName,
jsonStr
);
}
}
};

// 使用示例
Analytics.logEvent("level_complete", {
level: 5,
score: 1500,
stars: 3
});

场景三:获取设备信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Java 端
public class DeviceInfo {
public static String getDeviceId() {
// 获取 Android ID 或其他设备标识
return Settings.Secure.getString(
appContext.getContentResolver(),
Settings.Secure.ANDROID_ID
);
}

public static String getVersionName() {
try {
PackageInfo info = appContext.getPackageManager()
.getPackageInfo(appContext.getPackageName(), 0);
return info.versionName;
} catch (Exception e) {
return "unknown";
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// JavaScript 端
var Device = {
getDeviceId: function() {
if (cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID) {
return jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/DeviceInfo",
"getDeviceId",
"()Ljava/lang/String;"
);
}
return "web_" + Date.now();
},

getVersionName: function() {
if (cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID) {
return jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/DeviceInfo",
"getVersionName",
"()Ljava/lang/String;"
);
}
return "1.0.0";
}
};

Android UI 线程处理

核心问题

Cocos2d-x 的渲染和 JS 逻辑运行在 GL 线程(渲染线程),而 Android 的 UI 更新必须在主线程(UI 线程)执行。如果在 JS 中调用的 Java 方法需要更新 Android UI(如显示 Toast、Dialog、WebView 等),必须进行线程切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─────────────────────────────────────────────────────────────┐
│ 线程模型说明 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Cocos2d-x 线程 │
│ ┌─────────────────────────┐ │
│ │ GL 渲染线程 │ │
│ │ • JS 逻辑执行 │ │
│ │ • OpenGL 渲染 │ │
│ │ • 调用 Java 方法 │ │
│ └───────────┬─────────────┘ │
│ │ │
│ │ jsb.reflection.callStaticMethod │
│ ▼ │
│ Android 主线程 │
│ ┌─────────────────────────┐ │
│ │ UI 线程 │ │
│ │ • Activity 生命周期 │ │
│ │ • View 更新 │ │
│ │ • Dialog/Toast 显示 │ │
│ └─────────────────────────┘ │
│ │
│ 注意:Java 方法本身的执行在 GL 线程调用的 JNI 上下文中 │
│ 如需更新 UI,必须使用 runOnUiThread() 切换到 UI 线程 │
│ │
└─────────────────────────────────────────────────────────────┘

正确示例:显示 AlertDialog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package org.cocos2dx.javascript;

import android.app.AlertDialog;
import android.os.Bundle;
import org.cocos2dx.lib.Cocos2dxActivity;

public class AppActivity extends Cocos2dxActivity {

private static AppActivity app = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = this;
}

/**
* 显示系统对话框(线程安全)
*/
public static void showAlertDialog(final String title, final String message) {
// 必须在 UI 线程执行
app.runOnUiThread(new Runnable() {
@Override
public void run() {
AlertDialog alertDialog = new AlertDialog.Builder(app).create();
alertDialog.setTitle(title);
alertDialog.setMessage(message);
alertDialog.setIcon(R.drawable.icon);
alertDialog.show();
}
});
}
}
1
2
3
4
5
6
7
8
// JavaScript 调用
jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/AppActivity",
"showAlertDialog",
"(Ljava/lang/String;Ljava/lang/String;)V",
"提示",
"游戏数据已保存"
);

Toast 显示封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UIHelper {
private static AppActivity app = null;

public static void init(AppActivity activity) {
app = activity;
}

public static void showToast(final String message, final int duration) {
if (app == null) return;

app.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(app, message, duration).show();
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// JS 显示 Toast
function showToast(msg) {
if (cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID) {
jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/UIHelper",
"showToast",
"(Ljava/lang/String;I)V",
msg,
0 // Toast.LENGTH_SHORT = 0
);
} else {
cc.log("Toast: " + msg);
}
}

高级封装:通用桥接类

JavaScript 桥接工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Android 桥接工具
* 封装 jsb.reflection 调用,提供更友好的接口
*/
var AndroidBridge = {

_classPrefix: "org/cocos2dx/javascript/",

/**
* 调用 Java 静态方法
*/
call: function(className, methodName, signature) {
var args = Array.prototype.slice.call(arguments, 3);
var fullClassName = this._classPrefix + className;

try {
return jsb.reflection.callStaticMethod.apply(
null,
[fullClassName, methodName, signature].concat(args)
);
} catch (e) {
cc.error("AndroidBridge call failed:", e);
return null;
}
},

/**
* 检查是否在 Android 原生环境
*/
isAndroidNative: function() {
return cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID;
},

/**
* 安全的桥接调用(自动判断平台)
*/
safeCall: function(className, methodName, signature) {
if (!this.isAndroidNative()) {
cc.warn("Not running on Android native, call ignored");
return null;
}
var args = Array.prototype.slice.call(arguments, 3);
return this.call.apply(this, [className, methodName, signature].concat(args));
}
};

// 使用示例
if (AndroidBridge.isAndroidNative()) {
var result = AndroidBridge.call("Test", "sum", "(II)I", 10, 20);
cc.log("Result: " + result);
}

SDK 管理器封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* Android SDK 统一接口
*/
var AndroidSDK = {

// 初始化所有 SDK
init: function() {
if (!AndroidBridge.isAndroidNative()) return;

AndroidBridge.call("SdkManager", "init", "()V");
cc.log("Android SDK initialized");
},

// 支付
pay: function(productId, callback) {
if (!AndroidBridge.isAndroidNative()) {
callback && callback({ code: -1, msg: "Not Android native" });
return;
}

// 保存回调,等待 Java 层回调
this._payCallback = callback;

AndroidBridge.call("PayBridge", "pay", "(Ljava/lang/String;)V", productId);
},

// 统计事件
trackEvent: function(eventName, params) {
if (!AndroidBridge.isAndroidNative()) return;

var jsonStr = JSON.stringify(params || {});
AndroidBridge.call("AnalyticsBridge", "logEvent",
"(Ljava/lang/String;Ljava/lang/String;)V",
eventName, jsonStr);
},

// 显示 Toast
showToast: function(message) {
if (!AndroidBridge.isAndroidNative()) {
cc.log("Toast: " + message);
return;
}
AndroidBridge.call("UIHelper", "showToast",
"(Ljava/lang/String;I)V", message, 0);
},

// 获取设备信息
getDeviceInfo: function() {
if (!AndroidBridge.isAndroidNative()) {
return { platform: cc.sys.platform };
}

var deviceId = AndroidBridge.call("DeviceInfo", "getDeviceId",
"()Ljava/lang/String;") || "";
var version = AndroidBridge.call("DeviceInfo", "getVersionName",
"()Ljava/lang/String;") || "1.0.0";

return {
deviceId: deviceId,
version: version,
platform: "android"
};
},

_payCallback: null
};

常见问题与调试

问题一:方法签名错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 错误:String 签名缺少末尾分号
jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/Test",
"hello",
"(Ljava/lang/String)V", // 错误!缺少末尾的 ;
"msg"
);

// 正确
jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/Test",
"hello",
"(Ljava/lang/String;)V", // 注意末尾的分号
"msg"
);

问题二:类路径分隔符错误

1
2
3
4
5
// 错误:使用 . 分隔包名
"org.cocos2dx.javascript.Test"

// 正确:使用 / 分隔包名
"org/cocos2dx/javascript/Test"

问题三:重载方法匹配失败

1
2
3
// Java 端有两个 sum 方法
public static int sum(int a, int b) // 签名: (II)I
public static int sum(int a) // 签名: (I)I
1
2
3
4
5
6
7
// 必须根据参数数量选择正确的签名
var result = jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/Test",
"sum",
"(II)I", // 两个 int 参数
3, 7
);

调试技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 封装安全调用,带错误日志
function safeCallJava(className, methodName, signature) {
try {
var args = Array.prototype.slice.call(arguments, 3);
return jsb.reflection.callStaticMethod.apply(
null,
[className, methodName, signature].concat(args)
);
} catch (e) {
cc.error("Java call failed:");
cc.error(" Class:", className);
cc.error(" Method:", methodName);
cc.error(" Signature:", signature);
cc.error(" Error:", e.message);
return null;
}
}

JS 调用 Java vs C++ 调用 Java

特性 JS 调用 Java(jsb.reflection) C++ 调用 Java(JniHelper)
适用平台 Cocos2d-JS / Creator JSB Cocos2d-x C++
调用方式 反射调用静态方法 直接 JNI 调用
方法类型 仅限静态方法 静态和实例方法
参数类型 int, float, boolean, String 完整的 JNI 类型
返回值 int, float, boolean, String 任意 JNI 类型
线程注意 Java UI 操作需切到 UI 线程 同上

总结

Cocos2d-x JS 调用 Java 的核心要点:

  1. 核心 APIjsb.reflection.callStaticMethod(className, methodName, signature, ...args)
  2. 类路径格式:使用 / 分隔包名,如 org/cocos2dx/javascript/Test
  3. 方法签名(参数签名)返回值签名,String 类型为 Ljava/lang/String;(带分号)
  4. 支持类型:int(I)、float(F)、boolean(Z)、String(Ljava/lang/String;)
  5. 线程安全:Java 端更新 UI 必须使用 runOnUiThread() 切换到主线程
  6. 错误排查:注意方法签名中的分号、类路径分隔符、重载方法的签名匹配
  7. 生产封装:建议封装桥接工具类,统一处理平台判断和错误日志

通过合理的封装和线程处理,可以在 Cocos2d-JS 项目中高效集成 Android 平台的各类原生 SDK。