- Android 平台:这是最常见的需求,因为 C++ 代码无法直接在 Android 虚拟机上运行,必须通过 Java 作为桥梁来调用 Android SDK 的功能(如获取设备信息、调用系统服务、使用支付 SDK 等)。
- Java (JNI) 环境:如果你在 Cocos2d-x 项目中集成了某些使用 Java 编写的第三方库,并且需要从 C++ 端调用它们,也需要用到 JNI。
这两种场景的核心技术都是 Java Native Interface (JNI),下面我将详细讲解这两种情况的实现方法。

在 Cocos2d-x (C++) 中调用 Android Java 代码 (最常用)
这是开发 Android 游戏时最常遇到的需求,C++ 代码运行在原生层,而 Android 的系统 API 和大部分第三方 SDK 都在 Java 层,我们需要一个机制让 C++ 能“通知” Java 执行某些操作。
实现流程如下:
步骤 1:在 Java 层定义一个静态方法
这个静态方法将作为 C++ 调用的入口,我们会创建一个 Cocos2dxHelper.java 文件(如果项目没有,可以新建一个),或者利用项目中已有的 org.cocos2dx.lib.Cocos2dxHelper 类。
我们在 org.cocos2dx.lib.Cocos2dxHelper.java 中添加一个方法:

// 文件路径: your_project/src/org/cocos2dx/lib/Cocos2dxHelper.java
package org.cocos2dx.lib;
import android.content.Context;
import android.util.Log;
public class Cocos2dxHelper {
private static final String TAG = "Cocos2dxHelper";
// 获取 Cocos2dxHelper 的实例,或者使用 Context
private static Context sContext;
// 初始化方法,通常在 C++ 入口调用
public static void init(Context context) {
sContext = context;
}
// 定义一个静态的、公开的 native 方法,供 C++ 调用
// 注意:这个方法名和签名很重要,C++ 端要对应
public static void callFromCpp(String message, int number) {
Log.d(TAG, "Java: 收到来自 C++ 的消息: " + message);
Log.d(TAG, "Java: 收到来自 C++ 的数字: " + number);
// 在这里可以调用任何 Android API
// 显示一个 Toast
// Toast.makeText(sContext, "消息来自 C++: " + message, Toast.LENGTH_SHORT).show();
}
}
步骤 2:在 C++ 端使用 JNI 调用 Java 方法
Cocos2d-x 提供了一些便利的宏来简化 JNI 调用,最常用的是 JniHelper。
在 C++ 代码中调用:
#include "platform/android/jni/JniHelper.h"
#include <jni.h>
#include "cocos2d.h" // 包含必要的头文件
// 为了使用 cocos 的日志
using namespace cocos2d;
void callJavaFunction() {
// 1. 定义要调用的 Java 类的完整路径
const char* className = "org/cocos2dx/lib/Cocos2dxHelper";
// 2. 定义要调用的 Java 方法名
const char* methodName = "callFromCpp";
// 3. 定义方法的签名 (Method Signature)
// ()V 表示无参数,无返回值
// (Ljava/lang/String;I)V 表示两个参数:String 和 int,无返回值
// Ljava/lang/String; -> String
// I -> int
// V -> void
const char* methodSignature = "(Ljava/lang/String;I)V";
// 4. 准备要传递的参数
// JniHelper 提供了 JniMethodInfo 结构体来管理方法信息
JniMethodInfo methodInfo;
// 5. 获取 Java 方法信息
// static bool getStaticMethodInfo(JniMethodInfo& methodInfo,
// const char* className,
// const char* methodName,
// const char* paramCode);
if (JniHelper::getStaticMethodInfo(methodInfo, className, methodName, methodSignature)) {
// 6. 调用方法
// jstring (JNIEnv*, jstring)
jstring jMessage = methodInfo.env->NewStringUTF("Hello from C++!");
jint jNumber = 12345;
// 调用静态方法,传入参数
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jMessage, jNumber);
// 7. 释放局部引用
// JNI 函数返回的局部引用需要在当前线程中手动释放
methodInfo.env->DeleteLocalRef(jMessage);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
// 8. 销毁 JniMethodInfo 结构体
JniHelper::deleteLocalRef(methodInfo);
} else {
CCLOG("Failed to get static method info for %s.%s", className, methodName);
}
}
如何触发调用?
你可以在任何需要的地方调用 callJavaFunction(),比如在场景的初始化中:

// 在你的场景类中
void MyScene::onEnter() {
Scene::onEnter();
// 延迟一下调用,确保 Java 环境已完全初始化
this->scheduleOnce([this](float dt){
callJavaFunction();
}, 0.1f, "call_java");
}
在 Cocos2d-x (C++) 中调用一个自定义的 Java 类
这个场景与场景一类似,但如果你不想修改 Cocos2dxHelper,或者想调用一个独立的 Java 类,步骤如下:
步骤 1:创建自定义 Java 类
假设我们有一个 MyJavaBridge.java 文件。
// 文件路径: your_project/src/com/yourcompany/MyJavaBridge.java
package com.yourcompany;
import android.content.Context;
import android.util.Log;
public class MyJavaBridge {
private static final String TAG = "MyJavaBridge";
// 需要一个 Context 来操作 Android API
private Context mContext;
// 构造函数,用于传入 Context
public MyJavaBridge(Context context) {
this.mContext = context;
Log.d(TAG, "MyJavaBridge instance created.");
}
// 一个实例方法
public void showToast(String message) {
Log.d(TAG, "Showing toast: " + message);
// Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
}
// 一个静态方法
public static String getDeviceModel() {
return android.os.Build.MODEL;
}
}
步骤 2:在 C++ 中调用
调用实例方法和静态方法的方式略有不同。
调用静态方法 (和场景一类似):
void callStaticJavaMethod() {
const char* className = "com/yourcompany/MyJavaBridge";
const char* methodName = "getDeviceModel";
const char* methodSignature = "()Ljava/lang/String;"; // 无参数,返回 String
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, className, methodName, methodSignature)) {
jstring jResult = (jstring)methodInfo.env->CallStaticObjectMethod(methodInfo.classID, methodInfo.methodID);
// 将 jstring 转换为 C++ string
const char* resultStr = methodInfo.env->GetStringUTFChars(jResult, nullptr);
CCLOG("Device Model from Java: %s", resultStr);
// 释放资源
methodInfo.env->ReleaseStringUTFChars(jResult, resultStr);
methodInfo.env->DeleteLocalRef(jResult);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
JniHelper::deleteLocalRef(methodInfo);
}
}
调用实例方法 (需要先创建实例):
void callInstanceJavaMethod() {
const char* className = "com/yourcompany/MyJavaBridge";
// 1. 获取 Cocos2dxHelper 的 Context,因为我们的 Java 类需要它
JniMethodInfo helperMethodInfo;
if (JniHelper::getStaticMethodInfo(helperMethodInfo, "org/cocos2dx/lib/Cocos2dxHelper", "getContext", "()Landroid/content/Context;")) {
jobject contextObj = helperMethodInfo.env->CallStaticObjectMethod(helperMethodInfo.classID, helperMethodInfo.methodID);
// 2. 调用 MyJavaBridge 的构造函数创建实例
// 构造函数签名: (Landroid/content/Context;)V
JniMethodInfo constructorInfo;
if (JniHelper::getMethodInfo(constructorInfo, className, "<init>", "(Landroid/content/Context;)V")) {
// 创建一个 jobject 引用
jobject bridgeInstance = constructorInfo.env->NewObject(constructorInfo.classID, constructorInfo.methodID, contextObj);
// 3. 调用实例方法 showToast
JniMethodInfo methodInfo;
if (JniHelper::getMethodInfo(methodInfo, className, "showToast", "(Ljava/lang/String;)V")) {
jstring jMessage = methodInfo.env->NewStringUTF("This is an instance method call!");
methodInfo.env->CallVoidMethod(bridgeInstance, methodInfo.methodID, jMessage);
methodInfo.env->DeleteLocalRef(jMessage);
}
// 4. 清理
constructorInfo.env->DeleteLocalRef(bridgeInstance);
JniHelper::deleteLocalRef(constructorInfo);
}
// 清理 Context
helperMethodInfo.env->DeleteLocalRef(contextObj);
JniHelper::deleteLocalRef(helperMethodInfo);
}
}
重要注意事项
-
线程问题:Cocos2d-x 的主线程(UI 线程)和 Java 的主线程(UI 线程)是同一个,所有涉及 UI 操作的 Java 代码(如
Toast,Dialog)都必须在这个主线程中执行,如果你的 C++ 代码在后台线程中调用 Java,需要使用Handler将任务切换到主线程。// Java 端处理线程切换 public void showToastOnUiThread(final String message) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); } }); } -
签名:JNI 方法的签名 (
methodSignature) 是最容易出错的地方,可以使用javap工具来查看:# 在你的 app 模块的 build/intermediates/javac/debug/classes 目录下执行 javap -s com.yourcompany.MyJavaBridge
这会输出所有方法的签名,非常方便。
-
内存管理:JNI 函数返回的
jobject,jstring,jclass等都是局部引用,它们只在当前线程的当前方法调用中有效,如果需要在其他地方使用,或者跨线程使用,必须将其转换为全局引用 (NewGlobalRef),并在使用完毕后手动释放 (DeleteGlobalRef),对于简单的、一次性的调用,像上面的例子一样,在调用结束后用DeleteLocalRef释放即可。 -
路径:Java 类的路径是完整的包名 + 类名,用斜杠 分隔,而不是点 。
com.yourcompany.MyClass在 C++ 中是"com/yourcompany/MyClass"。
| 任务 | C++ 端 (JniHelper) | Java 端 |
|---|---|---|
| 调用静态方法 | getStaticMethodInfo() |
public static void myMethod(...) |
| 调用实例方法 | getMethodInfo() (用于构造) + NewObject() + getMethodInfo() (用于调用) |
public myMethod(...) + 需要通过构造函数创建实例 |
| 获取返回值 | CallStatic<Type>Method() |
方法返回相应类型 |
| 传递参数 | env->New<Type>(...) 创建 JNI 类型 |
方法接收相应类型参数 |
| 释放资源 | DeleteLocalRef() / DeleteGlobalRef() |
无需关心,由 JVM 管理 |
通过以上步骤,你就可以在 Cocos2d-x 的 C++ 代码中灵活地调用 Android 的 Java 功能了。
