Cocos Creator与Android原生代码交互完整指南

概述

在 Cocos Creator 游戏开发中,经常需要与 Android 原生平台进行交互,例如调用支付 SDK、分享功能、获取设备信息等。Cocos Creator 通过 JSB(JavaScript Binding)技术提供了 JavaScript 与 Java 之间的互相调用能力。本文将详细介绍如何在 Cocos Creator 中调用 Android 原生代码。

JSB 反射机制简介

Cocos Creator 使用 jsb.reflection 模块实现 JavaScript 与 Java 的通信。该模块提供了 callStaticMethod 方法,可以调用 Java 类中的静态方法。

callStaticMethod 方法签名

1
jsb.reflection.callStaticMethod(className, methodName, methodSignature, ...args)

参数说明:

  • className:Java 类的完整路径(使用斜杠代替点号)
  • methodName:要调用的静态方法名称
  • methodSignature:方法签名,描述参数类型和返回值类型
  • ...args:可变参数,传递给 Java 方法的实参

基础调用示例

JavaScript 端代码

1
2
3
4
5
6
7
8
9
if (jsb) {
let result = jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/AppActivity",
"pay",
"(Ljava/lang/String;)Ljava/lang/String;",
"1"
);
cc.log(result);
}

Java 端代码

AppActivity.java 中添加静态方法:

1
2
3
4
5
6
7
8
9
package org.cocos2dx.javascript;

public class AppActivity extends Cocos2dxActivity {

public static String pay(String index) {
// 处理支付逻辑
return "success";
}
}

方法签名详解

方法签名使用 JNI(Java Native Interface)格式,用于描述方法的参数类型和返回值类型。

基本类型对应表

JNI 类型 Java 类型
I int
F float
Z boolean
J long
D double
B byte
C char
S short
V void

引用类型表示法

  • 字符串:Ljava/lang/String;
  • 数组:[I(int 数组)、[Ljava/lang/String;(String 数组)

复杂方法签名示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 无参数,返回 void
"()V"

// String 参数,返回 int
"(Ljava/lang/String;)I"

// int 和 String 参数,返回 String
"(ILjava/lang/String;)Ljava/lang/String;"

// 多个参数示例
jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/AppActivity",
"shareContent",
"(Ljava/lang/String;Ljava/lang/String;I)V",
"标题",
"内容",
1
);

完整实战示例

1. 支付功能封装

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
public class AppActivity extends Cocos2dxActivity {

public static String startPay(String productId, String orderId) {
try {
// 调用支付 SDK
PaySDK.getInstance().pay(productId, orderId, new PayCallback() {
@Override
public void onSuccess() {
// 支付成功,通知 JS 层
Cocos2dxJavascriptJavaBridge.evalString(
"cc.director.emit('pay_success')"
);
}

@Override
public void onFail(int code, String msg) {
// 支付失败,通知 JS 层
Cocos2dxJavascriptJavaBridge.evalString(
"cc.director.emit('pay_fail', {code:" + code + ", msg:'" + msg + "'})"
);
}
});
return "processing";
} catch (Exception e) {
return "error: " + e.getMessage();
}
}
}

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
class PaymentHelper {
static init() {
// 监听支付结果
cc.director.on('pay_success', this.onPaySuccess, this);
cc.director.on('pay_fail', this.onPayFail, this);
}

static startPay(productId, orderId) {
if (!jsb) {
cc.warn("非原生平台,跳过支付");
return;
}

let result = jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/AppActivity",
"startPay",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
productId,
orderId
);

cc.log("支付调用结果:", result);
}

static onPaySuccess() {
cc.log("支付成功!");
// 发放道具
}

static onPayFail(data) {
cc.error("支付失败:", data.code, data.msg);
}
}

module.exports = PaymentHelper;

2. 获取设备信息

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static String getDeviceInfo() {
JSONObject info = new JSONObject();
try {
info.put("deviceModel", Build.MODEL);
info.put("systemVersion", Build.VERSION.RELEASE);
info.put("sdkVersion", Build.VERSION.SDK_INT);
info.put("packageName", getPackageName());
info.put("versionName", getVersionName());
} catch (JSONException e) {
e.printStackTrace();
}
return info.toString();
}

JavaScript 代码:

1
2
3
4
5
6
7
8
9
10
11
getDeviceInfo() {
if (!jsb) return null;

let infoStr = jsb.reflection.callStaticMethod(
"org/cocos2dx/javascript/AppActivity",
"getDeviceInfo",
"()Ljava/lang/String;"
);

return JSON.parse(infoStr);
}

Java 调用 JavaScript

除了 JS 调用 Java,有时也需要从 Java 层回调 JavaScript。

使用 Cocos2dxJavascriptJavaBridge

1
2
3
4
5
6
7
8
9
10
11
import org.cocos2dx.lib.Cocos2dxJavascriptJavaBridge;

// 在主线程中调用
runOnGLThread(new Runnable() {
@Override
public void run() {
Cocos2dxJavascriptJavaBridge.evalString(
"window.onAndroidCallback && window.onAndroidCallback('参数')"
);
}
});

调用带参数的 JS 函数

1
2
3
4
5
6
7
8
9
10
11
public void notifyGameEvent(String event, String data) {
final String jsCode = String.format(
"cc.director.emit('%s', '%s')",
event,
data
);

runOnGLThread(() -> {
Cocos2dxJavascriptJavaBridge.evalString(jsCode);
});
}

常见问题与解决方案

1. 类找不到错误

问题现象: java.lang.ClassNotFoundException

解决方案:

  • 检查类路径是否正确(使用斜杠 / 而非点号 .
  • 确保类在 src 目录下
  • 检查包名声明是否与路径一致

2. 方法签名错误

问题现象: java.lang.NoSuchMethodError

解决方案:

  • 确认方法签名格式正确
  • 检查参数类型是否匹配
  • 确保方法是 public static 修饰

3. 在非 Android 平台报错

问题现象: 在浏览器或桌面端运行时出现 jsb is not defined

解决方案:

1
2
3
4
5
if (jsb && jsb.reflection) {
// 调用原生方法
} else {
cc.log("当前平台不支持原生调用");
}

4. 主线程问题

某些 Android 操作需要在主线程执行:

1
2
3
4
5
6
runOnUiThread(new Runnable() {
@Override
public void run() {
// 执行 UI 相关操作
}
});

最佳实践

1. 封装原生接口

建议将原生调用封装成独立的模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// NativeHelper.js
export default class NativeHelper {
static isNative() {
return jsb && jsb.reflection;
}

static callAndroid(className, method, signature, ...args) {
if (!this.isNative()) {
cc.warn("当前不是原生平台");
return null;
}

try {
return jsb.reflection.callStaticMethod(
className, method, signature, ...args
);
} catch (e) {
cc.error("原生调用失败:", e);
return null;
}
}
}

2. 回调处理机制

使用事件系统处理异步回调:

1
2
3
4
5
6
7
8
9
10
11
// 定义事件名称
export const NativeEvents = {
PAY_SUCCESS: 'native_pay_success',
PAY_FAIL: 'native_pay_fail',
SHARE_COMPLETE: 'native_share_complete'
};

// Android 层触发事件
// Cocos2dxJavascriptJavaBridge.evalString(
// "cc.director.emit('native_pay_success', {orderId:'123'})"
// );

3. 调试技巧

在 Java 端添加日志便于调试:

1
2
3
4
5
6
7
import android.util.Log;

public static String testMethod(String param) {
Log.d("CocosCreator", "收到参数: " + param);
// ... 处理逻辑
return "result";
}

使用 adb logcat -s CocosCreator:D 查看日志。

总结

通过 jsb.reflection.callStaticMethod,Cocos Creator 可以方便地与 Android 原生平台进行通信。关键点包括:

  1. 正确使用方法签名:理解 JNI 类型标识符的含义
  2. 做好平台判断:确保只在原生平台执行相关代码
  3. 封装调用接口:将原生调用封装成易用的模块
  4. 处理异步回调:使用事件机制处理 Java 到 JS 的通信

掌握这些技术,可以大大扩展 Cocos Creator 游戏的功能边界,实现更丰富的原生平台能力集成。